// upload.js β Frontend file upload handler import { showToast } from "./toast.js"; import { playBeep } from "./sound.js"; import { logToServer } from "./logger.js"; // Initialize upload system when DOM is loaded document.addEventListener('DOMContentLoaded', () => { const dropzone = document.getElementById("user-upload-area"); if (dropzone) { dropzone.setAttribute("aria-label", "Upload area. Click or drop an audio file to upload."); } const fileInput = document.getElementById("fileInputUser"); const fileInfo = document.createElement("div"); fileInfo.id = "file-info"; fileInfo.style.textAlign = "center"; if (fileInput) { fileInput.parentNode.insertBefore(fileInfo, fileInput.nextSibling); } const streamInfo = document.getElementById("stream-info"); const streamUrlEl = document.getElementById("streamUrl"); const spinner = document.getElementById("spinner") || { style: { display: 'none' } }; let abortController; // Upload function const upload = async (file) => { if (abortController) abortController.abort(); abortController = new AbortController(); fileInfo.innerText = `π ${file.name} β’ ${(file.size / 1024 / 1024).toFixed(2)} MB`; if (file.size > 100 * 1024 * 1024) { showToast("β File too large. Please upload a file smaller than 100MB."); return; } spinner.style.display = "block"; showToast('π‘ Uploadingβ¦'); fileInput.disabled = true; dropzone.classList.add("uploading"); const formData = new FormData(); const sessionUid = localStorage.getItem("uid"); formData.append("uid", sessionUid); formData.append("file", file); const res = await fetch("/upload", { signal: abortController.signal, method: "POST", body: formData, }); let data, parseError; try { data = await res.json(); } catch (e) { parseError = e; } if (!data) { showToast("β Upload failed: " + (parseError && parseError.message ? parseError.message : "Unknown error")); spinner.style.display = "none"; fileInput.disabled = false; dropzone.classList.remove("uploading"); return; } if (res.ok) { if (data.quota && data.quota.used_mb !== undefined) { const bar = document.getElementById("quota-bar"); const text = document.getElementById("quota-text"); const quotaSec = document.getElementById("quota-meter"); if (bar && text && quotaSec) { quotaSec.hidden = false; const used = parseFloat(data.quota.used_mb); bar.value = used; bar.max = 100; text.textContent = `${used.toFixed(1)} MB used`; } } spinner.style.display = "none"; fileInput.disabled = false; dropzone.classList.remove("uploading"); showToast("β Upload successful."); // Refresh the audio player and file list const uid = localStorage.getItem("uid"); if (uid) { try { if (window.loadProfileStream) { await window.loadProfileStream(uid); } // Refresh the file list if (window.fetchAndDisplayFiles) { await window.fetchAndDisplayFiles(uid); } // Refresh the stream list to update the last update time if (window.refreshStreamList) { await window.refreshStreamList(); } } catch (e) { console.error('Failed to refresh:', e); } } playBeep(432, 0.25, "sine"); } else { if (streamInfo) streamInfo.hidden = true; if (spinner) spinner.style.display = "none"; if ((data.detail || data.error || "").includes("music")) { showToast("π΅ Upload rejected: singing or music detected."); } else { showToast(`β Upload failed: ${data.detail || data.error}`); } if (fileInput) fileInput.value = null; if (dropzone) dropzone.classList.remove("uploading"); if (fileInput) fileInput.disabled = false; if (streamInfo) streamInfo.classList.remove("visible", "slide-in"); } }; // Function to fetch and display uploaded files async function fetchAndDisplayFiles(uidFromParam) { console.log('[UPLOAD] fetchAndDisplayFiles called with uid:', uidFromParam); // Get the file list element const fileList = document.getElementById('file-list'); if (!fileList) { const errorMsg = 'File list element not found in DOM'; console.error(errorMsg); return showErrorInUI(errorMsg); } // Get UID from parameter, localStorage, or cookie const uid = uidFromParam || localStorage.getItem('uid') || getCookie('uid'); const authToken = localStorage.getItem('authToken'); const headers = { 'Accept': 'application/json', }; // Include auth token in headers if available, but don't fail if it's not // The server should handle both token-based and UID-based auth if (authToken) { headers['Authorization'] = `Bearer ${authToken}`; } else { console.debug('[UPLOAD] No auth token available, using UID-only authentication'); } console.log('[UPLOAD] Auth state - UID:', uid, 'Token exists:', !!authToken); if (!uid) { console.error('[UPLOAD] No UID found in any source'); fileList.innerHTML = '
'; return; } // Log the authentication method being used if (!authToken) { console.debug('[UPLOAD] No auth token found, using UID-only authentication'); } else { console.debug('[UPLOAD] Using token-based authentication'); } // Show loading state fileList.innerHTML = ' '; try { console.log(`[DEBUG] Fetching files for user: ${uid}`); const response = await fetch(`/me/${uid}`, { headers: { 'Authorization': authToken ? `Bearer ${authToken}` : '', 'Content-Type': 'application/json', }, }); console.log('[DEBUG] Response status:', response.status, response.statusText); if (!response.ok) { const errorText = await response.text(); const errorMsg = `Failed to fetch files: ${response.status} ${response.statusText} - ${errorText}`; console.error(`[ERROR] ${errorMsg}`); throw new Error(errorMsg); } const data = await response.json(); console.log('[DEBUG] Received files data:', data); if (!data.files) { throw new Error('Invalid response format: missing files array'); } if (data.files.length > 0) { // Sort files by name const sortedFiles = [...data.files].sort((a, b) => a.name.localeCompare(b.name)); fileList.innerHTML = sortedFiles.map(file => { const sizeMB = (file.size / (1024 * 1024)).toFixed(2); const displayName = file.original_name || file.name; const isRenamed = file.original_name && file.original_name !== file.name; return `