Fix upload 'processing' status

This commit is contained in:
inubimambo
2025-07-06 23:39:32 +08:00
parent f5f5189fa0
commit b2b12a4197
3 changed files with 335 additions and 61 deletions

182
server.js
View File

@@ -634,7 +634,7 @@ app.post('/upload', requireAuth, upload.single('noteFile'), async (req, res) =>
console.log('Response sent, starting async processing...'); console.log('Response sent, starting async processing...');
// Process document asynchronously // Process document asynchronously
processDocumentAsync(fileInfo, req.session.userId); processDocumentAsync(fileInfo, req.session.userId, req.session);
} catch (error) { } catch (error) {
console.error('Upload error:', error); console.error('Upload error:', error);
@@ -647,13 +647,24 @@ app.post('/upload', requireAuth, upload.single('noteFile'), async (req, res) =>
}); });
// Async function to process document and upsert to Flowise using FormData // Async function to process document and upsert to Flowise using FormData
async function processDocumentAsync(fileInfo, userId) { async function processDocumentAsync(fileInfo, userId, session) {
try { try {
console.log('=== DOCUMENT PROCESSING START (FormData) ==='); console.log('=== DOCUMENT PROCESSING START (FormData) ===');
console.log(`Starting document processing for: ${fileInfo.originalName}`); console.log(`Starting document processing for: ${fileInfo.originalName}`);
console.log(`File path: ${fileInfo.path}`); console.log(`File path: ${fileInfo.path}`);
console.log(`File size: ${fileInfo.size} bytes`); console.log(`File size: ${fileInfo.size} bytes`);
console.log(`User ID: ${userId}`); console.log(`User ID: ${userId}`);
console.log(`Session ID in processing: ${session.id || 'undefined'}`);
// Find the file in the session to update it by reference
const sessionFile = session.uploadedFiles.find(f => f.id === fileInfo.id);
if (!sessionFile) {
console.error('File not found in session:', fileInfo.id);
console.error('Available files in session:', session.uploadedFiles.map(f => ({ id: f.id, name: f.originalName })));
return;
}
console.log('Found session file for update:', { id: sessionFile.id, name: sessionFile.originalName, currentStatus: sessionFile.status });
// Create document metadata // Create document metadata
const documentMetadata = { const documentMetadata = {
@@ -668,7 +679,7 @@ async function processDocumentAsync(fileInfo, userId) {
console.log('Document metadata:', documentMetadata); console.log('Document metadata:', documentMetadata);
// Initialize processing result // Initialize processing result
fileInfo.processingResult = { sessionFile.processingResult = {
totalChunks: 1, // FormData uploads are single operations totalChunks: 1, // FormData uploads are single operations
successfulChunks: 0, successfulChunks: 0,
failedChunks: 0, failedChunks: 0,
@@ -683,41 +694,71 @@ async function processDocumentAsync(fileInfo, userId) {
console.log('FormData upsert result:', upsertResult); console.log('FormData upsert result:', upsertResult);
// Update file info with processing results // Update session file with processing results
fileInfo.status = upsertResult.success ? 'processed' : 'failed'; sessionFile.status = upsertResult.success ? 'processed' : 'failed';
fileInfo.processingProgress = null; // Clear progress when done sessionFile.processingProgress = null; // Clear progress when done
fileInfo.processingResult = { sessionFile.processingResult = {
totalChunks: upsertResult.totalChunks, totalChunks: upsertResult.totalChunks,
successfulChunks: upsertResult.successfulChunks, successfulChunks: upsertResult.successfulChunks,
failedChunks: upsertResult.failedChunks, failedChunks: upsertResult.failedChunks,
processedAt: new Date().toISOString(), processedAt: new Date().toISOString(),
startedAt: fileInfo.processingResult.startedAt, startedAt: sessionFile.processingResult.startedAt,
duration: Date.now() - new Date(fileInfo.processingResult.startedAt).getTime(), duration: Date.now() - new Date(sessionFile.processingResult.startedAt).getTime(),
method: 'formdata' method: 'formdata'
}; };
if (upsertResult.errors.length > 0) { if (upsertResult.errors.length > 0) {
fileInfo.processingErrors = upsertResult.errors; sessionFile.processingErrors = upsertResult.errors;
console.log(`Processing errors for ${fileInfo.originalName}:`, upsertResult.errors); console.log(`Processing errors for ${sessionFile.originalName}:`, upsertResult.errors);
} }
console.log(`Document processing completed for: ${fileInfo.originalName}`); console.log(`Document processing completed for: ${sessionFile.originalName}`);
console.log(`Result: FormData upload ${upsertResult.success ? 'successful' : 'failed'}`); console.log(`Result: FormData upload ${upsertResult.success ? 'successful' : 'failed'}`);
console.log(`Updated session file status to: ${sessionFile.status}`);
// Verify the session file was updated
console.log('Session file after update:', { id: sessionFile.id, name: sessionFile.originalName, status: sessionFile.status });
console.log('All session files after update:', session.uploadedFiles.map(f => ({ id: f.id, name: f.originalName, status: f.status })));
// Force session save since we modified it in an async context
session.save((err) => {
if (err) {
console.error('Error saving session after document processing:', err);
} else {
console.log('Session saved successfully after document processing');
}
});
// Log final upsert verification // Log final upsert verification
console.log('Final FormData upsert verification:', { console.log('Final FormData upsert verification:', {
documentStore: `${FLOWISE_BASE_URL}/api/v1/document-store/${FLOWISE_DOCUMENT_STORE_ID}`, documentStore: `${FLOWISE_BASE_URL}/api/v1/document-store/${FLOWISE_DOCUMENT_STORE_ID}`,
documentId: fileInfo.id, documentId: sessionFile.id,
fileName: fileInfo.originalName, fileName: sessionFile.originalName,
success: upsertResult.success success: upsertResult.success,
finalStatus: sessionFile.status
}); });
} catch (error) { } catch (error) {
console.error(`Error processing document ${fileInfo.originalName}:`, error); console.error(`Error processing document ${fileInfo.originalName}:`, error);
fileInfo.status = 'failed';
fileInfo.processingError = error.message; // Find the file in the session to update it with error
fileInfo.processingResult = fileInfo.processingResult || {}; const sessionFile = session.uploadedFiles.find(f => f.id === fileInfo.id);
fileInfo.processingResult.failedAt = new Date().toISOString(); if (sessionFile) {
sessionFile.status = 'failed';
sessionFile.processingError = error.message;
sessionFile.processingResult = sessionFile.processingResult || {};
sessionFile.processingResult.failedAt = new Date().toISOString();
console.log(`Updated session file status to: ${sessionFile.status} due to error`);
// Force session save since we modified it in an async context
session.save((err) => {
if (err) {
console.error('Error saving session after processing error:', err);
} else {
console.log('Session saved successfully after processing error');
}
});
}
} }
} }
@@ -806,34 +847,115 @@ app.post('/api/revise', requireAuth, async (req, res) => {
}); });
app.get('/chat', requireAuth, (req, res) => { app.get('/chat', requireAuth, (req, res) => {
// Initialize chat history if it doesn't exist
if (!req.session.chatHistory) {
req.session.chatHistory = [];
}
res.render('chat', { res.render('chat', {
title: 'Chat with EduCat AI' title: 'Chat with EduCat AI',
chatHistory: req.session.chatHistory
}); });
}); });
app.post('/api/chat', requireAuth, async (req, res) => { app.post('/api/chat', requireAuth, async (req, res) => {
try { try {
const { message, history } = req.body; const { message } = req.body;
// Call Flowise API for chat // Initialize chat history in session if it doesn't exist
if (!req.session.chatHistory) {
req.session.chatHistory = [];
}
console.log('Chat message received:', message);
console.log('Current chat history length:', req.session.chatHistory.length);
// Call Flowise API for chat with session history
const response = await axios.post(`${FLOWISE_API_URL}/${FLOWISE_CHATFLOW_ID}`, { const response = await axios.post(`${FLOWISE_API_URL}/${FLOWISE_CHATFLOW_ID}`, {
question: message, question: message,
history: history || [] history: req.session.chatHistory
}); });
const aiResponse = response.data.text || response.data.answer || 'No response received';
// Add the conversation to session history
req.session.chatHistory.push({
human: message,
ai: aiResponse
});
// Save session explicitly since we modified it
req.session.save((err) => {
if (err) {
console.error('Error saving chat session:', err);
}
});
console.log('Updated chat history length:', req.session.chatHistory.length);
res.json({ res.json({
success: true, success: true,
response: response.data.text || response.data.answer || 'No response received' response: aiResponse
}); });
} catch (error) { } catch (error) {
console.error('Chat error:', error); console.error('Chat error:', error);
res.status(500).json({ res.status(500).json({
success: false,
error: 'Failed to get chat response', error: 'Failed to get chat response',
details: error.message details: error.message
}); });
} }
}); });
// Get chat history from session
app.get('/api/chat/history', requireAuth, (req, res) => {
try {
const chatHistory = req.session.chatHistory || [];
console.log('Chat history requested, returning', chatHistory.length, 'messages');
res.json({
success: true,
history: chatHistory
});
} catch (error) {
console.error('Error getting chat history:', error);
res.status(500).json({
success: false,
error: 'Failed to get chat history',
details: error.message
});
}
});
// Clear chat history
app.delete('/api/chat/history', requireAuth, (req, res) => {
try {
req.session.chatHistory = [];
req.session.save((err) => {
if (err) {
console.error('Error clearing chat session:', err);
return res.status(500).json({
success: false,
error: 'Failed to clear chat history'
});
}
console.log('Chat history cleared for user:', req.session.userId);
res.json({
success: true,
message: 'Chat history cleared'
});
});
} catch (error) {
console.error('Error clearing chat history:', error);
res.status(500).json({
success: false,
error: 'Failed to clear chat history',
details: error.message
});
}
});
app.get('/dashboard', requireAuth, (req, res) => { app.get('/dashboard', requireAuth, (req, res) => {
const files = req.session.uploadedFiles || []; const files = req.session.uploadedFiles || [];
res.render('dashboard', { res.render('dashboard', {
@@ -921,6 +1043,12 @@ app.get('/api/files/:fileId/status', requireAuth, async (req, res) => {
const files = req.session.uploadedFiles || []; const files = req.session.uploadedFiles || [];
const file = files.find(f => f.id === fileId); const file = files.find(f => f.id === fileId);
console.log(`Status requested for file ${fileId}`);
console.log(`Session ID: ${req.session.id || req.sessionID}`);
console.log(`User ID: ${req.session.userId}`);
console.log(`Files in session:`, files.map(f => ({ id: f.id, name: f.originalName, status: f.status })));
console.log(`Found file:`, file ? { id: file.id, name: file.originalName, status: file.status } : 'Not found');
if (!file) { if (!file) {
return res.status(404).json({ success: false, error: 'File not found' }); return res.status(404).json({ success: false, error: 'File not found' });
} }
@@ -969,7 +1097,7 @@ app.post('/api/files/:fileId/retry', requireAuth, async (req, res) => {
file.processingErrors = null; file.processingErrors = null;
// Start processing asynchronously // Start processing asynchronously
processDocumentAsync(file, req.session.userId); processDocumentAsync(file, req.session.userId, req.session);
res.json({ res.json({
success: true, success: true,
@@ -989,6 +1117,8 @@ app.post('/api/files/:fileId/retry', requireAuth, async (req, res) => {
app.get('/api/files/status/all', requireAuth, async (req, res) => { app.get('/api/files/status/all', requireAuth, async (req, res) => {
try { try {
const files = req.session.uploadedFiles || []; const files = req.session.uploadedFiles || [];
console.log('Status check requested for session files:', files.map(f => ({ id: f.id, name: f.originalName, status: f.status })));
const statusSummary = files.map(file => ({ const statusSummary = files.map(file => ({
id: file.id, id: file.id,
originalName: file.originalName, originalName: file.originalName,
@@ -1010,6 +1140,8 @@ app.get('/api/files/status/all', requireAuth, async (req, res) => {
files: statusSummary files: statusSummary
}; };
console.log('Returning status summary:', summary);
res.json({ res.json({
success: true, success: true,
summary: summary summary: summary

View File

@@ -5,23 +5,16 @@
<div class="col-lg-8"> <div class="col-lg-8">
<div class="card shadow-lg border-0 chat"> <div class="card shadow-lg border-0 chat">
<div class="card-header bg-primary text-white"> <div class="card-header bg-primary text-white">
<h3 class="mb-0"><i class="fas fa-comments me-2"></i>Chat with EduCat AI</h3> <div class="d-flex justify-content-between align-items-center">
<h3 class="mb-0"><i class="fas fa-comments me-2"></i>Chat with EduCat AI</h3>
<button type="button" id="clear-chat-btn" class="btn btn-outline-light btn-sm" title="Clear Chat History">
<i class="fas fa-trash-alt me-1"></i>Clear
</button>
</div>
</div> </div>
<div class="card-body p-0"> <div class="card-body p-0">
<div id="chat-container" class="p-4" style="height: 500px; overflow-y: auto;"> <div id="chat-container" class="p-4" style="height: 500px; overflow-y: auto;">
<div class="chat-message bot-message mb-3"> <!-- Initial bot message will be added here if no history exists -->
<div class="d-flex align-items-start">
<div class="avatar bg-primary text-white rounded-circle d-flex align-items-center justify-content-center me-3 flex-shrink-0" style="width: 45px; height: 45px; min-width: 45px;">
<i class="fas fa-cat" style="font-size: 1.2rem;"></i>
</div>
<div class="message-content flex-grow-1">
<div class="message-bubble bg-light p-3 rounded-3 shadow-sm border">
<p class="mb-0">Hello! I'm EduCat AI, your study assistant. I can help you with questions about your notes, study techniques, and academic topics. How can I assist you today?</p>
</div>
<small class="text-muted d-block mt-1">Just now</small>
</div>
</div>
</div>
</div> </div>
<div class="border-top bg-light p-3"> <div class="border-top bg-light p-3">
<div class="input-group"> <div class="input-group">
@@ -59,12 +52,17 @@
</div> </div>
<script> <script>
let chatHistory = []; // Get chat history from server-side rendered data
const serverChatHistory = <%- JSON.stringify(chatHistory || []) %>;
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
const chatContainer = document.getElementById('chat-container'); const chatContainer = document.getElementById('chat-container');
const chatInput = document.getElementById('chat-input'); const chatInput = document.getElementById('chat-input');
const sendBtn = document.getElementById('send-btn'); const sendBtn = document.getElementById('send-btn');
const clearChatBtn = document.getElementById('clear-chat-btn');
// Load existing chat history from server
loadChatHistory();
sendBtn.addEventListener('click', sendMessage); sendBtn.addEventListener('click', sendMessage);
chatInput.addEventListener('keypress', function(e) { chatInput.addEventListener('keypress', function(e) {
@@ -73,6 +71,45 @@ document.addEventListener('DOMContentLoaded', function() {
} }
}); });
clearChatBtn.addEventListener('click', clearChatHistory);
function loadChatHistory() {
// Clear the container first
chatContainer.innerHTML = '';
if (serverChatHistory.length === 0) {
// Show initial welcome message if no history exists
addWelcomeMessage();
} else {
// Load all previous messages
serverChatHistory.forEach(conversation => {
addMessageToChat('user', conversation.human, false);
addMessageToChat('bot', conversation.ai, false);
});
}
chatContainer.scrollTop = chatContainer.scrollHeight;
}
function addWelcomeMessage() {
const messageDiv = document.createElement('div');
messageDiv.className = 'chat-message bot-message mb-3';
messageDiv.innerHTML = `
<div class="d-flex align-items-start">
<div class="avatar bg-primary text-white rounded-circle d-flex align-items-center justify-content-center me-3 flex-shrink-0" style="width: 45px; height: 45px; min-width: 45px;">
<i class="fas fa-cat" style="font-size: 1.2rem;"></i>
</div>
<div class="message-content flex-grow-1">
<div class="message-bubble bg-light p-3 rounded-3 shadow-sm border">
<p class="mb-0">Hello! I'm EduCat AI, your study assistant. I can help you with questions about your notes, study techniques, and academic topics. How can I assist you today?</p>
</div>
<small class="text-muted d-block mt-1">Just now</small>
</div>
</div>
`;
chatContainer.appendChild(messageDiv);
}
function sendMessage() { function sendMessage() {
const message = chatInput.value.trim(); const message = chatInput.value.trim();
if (!message) return; if (!message) return;
@@ -88,8 +125,7 @@ document.addEventListener('DOMContentLoaded', function() {
sendToAI(message); sendToAI(message);
} }
function addMessageToChat(sender, message) { function addMessageToChat(sender, message, withScroll = true) {
const chatContainer = document.getElementById('chat-container');
const messageDiv = document.createElement('div'); const messageDiv = document.createElement('div');
messageDiv.className = `chat-message ${sender}-message mb-3`; messageDiv.className = `chat-message ${sender}-message mb-3`;
@@ -126,11 +162,12 @@ document.addEventListener('DOMContentLoaded', function() {
} }
chatContainer.appendChild(messageDiv); chatContainer.appendChild(messageDiv);
chatContainer.scrollTop = chatContainer.scrollHeight; if (withScroll) {
chatContainer.scrollTop = chatContainer.scrollHeight;
}
} }
function addTypingIndicator() { function addTypingIndicator() {
const chatContainer = document.getElementById('chat-container');
const typingDiv = document.createElement('div'); const typingDiv = document.createElement('div');
typingDiv.id = 'typing-indicator'; typingDiv.id = 'typing-indicator';
typingDiv.className = 'chat-message bot-message mb-3'; typingDiv.className = 'chat-message bot-message mb-3';
@@ -170,8 +207,7 @@ document.addEventListener('DOMContentLoaded', function() {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
}, },
body: JSON.stringify({ body: JSON.stringify({
message: message, message: message
history: chatHistory
}) })
}); });
@@ -181,7 +217,6 @@ document.addEventListener('DOMContentLoaded', function() {
if (result.success) { if (result.success) {
addMessageToChat('bot', result.response); addMessageToChat('bot', result.response);
chatHistory.push({human: message, ai: result.response});
} else { } else {
addMessageToChat('bot', 'Sorry, I encountered an error. Please try again.'); addMessageToChat('bot', 'Sorry, I encountered an error. Please try again.');
} }
@@ -190,6 +225,31 @@ document.addEventListener('DOMContentLoaded', function() {
addMessageToChat('bot', 'Sorry, I\'m having trouble connecting right now. Please try again.'); addMessageToChat('bot', 'Sorry, I\'m having trouble connecting right now. Please try again.');
} }
} }
async function clearChatHistory() {
if (!confirm('Are you sure you want to clear the chat history? This action cannot be undone.')) {
return;
}
try {
const response = await fetch('/api/chat/history', {
method: 'DELETE'
});
const result = await response.json();
if (result.success) {
// Clear the chat container and show welcome message
chatContainer.innerHTML = '';
addWelcomeMessage();
chatContainer.scrollTop = chatContainer.scrollHeight;
} else {
alert('Failed to clear chat history. Please try again.');
}
} catch (error) {
alert('Error clearing chat history. Please try again.');
}
}
}); });
</script> </script>

View File

@@ -5,9 +5,14 @@
<div class="col-12"> <div class="col-12">
<div class="d-flex justify-content-between align-items-center mb-4"> <div class="d-flex justify-content-between align-items-center mb-4">
<h2><i class="fas fa-tachometer-alt me-2"></i>Your Dashboard</h2> <h2><i class="fas fa-tachometer-alt me-2"></i>Your Dashboard</h2>
<a href="/upload" class="btn btn-primary"> <div>
<i class="fas fa-plus me-2"></i>Upload New Notes <button class="btn btn-outline-secondary me-2" onclick="refreshStatus()" id="refreshBtn">
</a> <i class="fas fa-sync-alt me-1"></i>Refresh Status
</button>
<a href="/upload" class="btn btn-primary">
<i class="fas fa-plus me-2"></i>Upload New Notes
</a>
</div>
</div> </div>
<% if (files.length === 0) { %> <% if (files.length === 0) { %>
@@ -497,6 +502,49 @@ async function viewProcessingDetails(fileId) {
// Progress monitoring for processing files // Progress monitoring for processing files
let progressMonitoring = {}; let progressMonitoring = {};
// Manual refresh function
async function refreshStatus() {
const refreshBtn = document.getElementById('refreshBtn');
const originalHtml = refreshBtn.innerHTML;
refreshBtn.innerHTML = '<i class="fas fa-spinner fa-spin me-1"></i>Refreshing...';
refreshBtn.disabled = true;
try {
const response = await fetch('/api/files/status/all');
const result = await response.json();
if (result.success) {
console.log('Status refresh successful:', result.summary);
// Check if status has changed - if so, reload page to show updates
const hasProcessingFiles = result.summary.processing > 0;
const currentProcessingBadges = document.querySelectorAll('.badge').length;
if (!hasProcessingFiles || result.summary.processed > 0) {
console.log('Status changed, reloading page...');
location.reload();
} else {
// Show success feedback
refreshBtn.innerHTML = '<i class="fas fa-check me-1"></i>Updated';
setTimeout(() => {
refreshBtn.innerHTML = originalHtml;
refreshBtn.disabled = false;
}, 1500);
}
} else {
throw new Error(result.error || 'Failed to refresh status');
}
} catch (error) {
console.error('Error refreshing status:', error);
refreshBtn.innerHTML = '<i class="fas fa-exclamation-triangle me-1"></i>Error';
setTimeout(() => {
refreshBtn.innerHTML = originalHtml;
refreshBtn.disabled = false;
}, 2000);
}
}
function startProgressMonitoring() { function startProgressMonitoring() {
// Find all processing files and start monitoring them // Find all processing files and start monitoring them
const processingCards = document.querySelectorAll('.card'); const processingCards = document.querySelectorAll('.card');
@@ -534,19 +582,19 @@ function startFileProgressMonitoring(fileId) {
// Stop monitoring if processing is complete // Stop monitoring if processing is complete
if (result.progress.status !== 'processing') { if (result.progress.status !== 'processing') {
console.log(`File ${fileId} finished processing with status: ${result.progress.status}`);
clearInterval(progressMonitoring[fileId]); clearInterval(progressMonitoring[fileId]);
delete progressMonitoring[fileId]; delete progressMonitoring[fileId];
// Refresh page to show final status // Reload page immediately to show final status
setTimeout(() => { console.log('Reloading page to show updated status...');
location.reload(); location.reload();
}, 2000);
} }
} }
} catch (error) { } catch (error) {
console.error('Error checking progress:', error); console.error('Error checking progress:', error);
} }
}, 2000); // Check every 2 seconds }, 1000); // Check every 1 second for faster updates
} }
function updateProgressDisplay(fileId, progress) { function updateProgressDisplay(fileId, progress) {
@@ -631,7 +679,7 @@ function stopAllProgressMonitoring() {
// Clean up when page unloads // Clean up when page unloads
window.addEventListener('beforeunload', stopAllProgressMonitoring); window.addEventListener('beforeunload', stopAllProgressMonitoring);
// Auto-refresh processing status every 10 seconds for files that are still processing // Auto-refresh processing status for files that are still processing
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
// Start progress monitoring for processing files // Start progress monitoring for processing files
startProgressMonitoring(); startProgressMonitoring();
@@ -648,8 +696,31 @@ document.addEventListener('DOMContentLoaded', function() {
if (hasProcessingFiles) { if (hasProcessingFiles) {
console.log('Found processing files, setting up auto-refresh...'); console.log('Found processing files, setting up auto-refresh...');
setInterval(() => {
// Check again if there are still processing files // More frequent checking - every 3 seconds
const statusCheckInterval = setInterval(async () => {
console.log('Checking file statuses...');
try {
const response = await fetch('/api/files/status/all');
const result = await response.json();
if (result.success) {
console.log('Status check result:', result.summary);
// If no more processing files, reload page to show final status
if (result.summary.processing === 0) {
console.log('No more processing files, reloading page...');
clearInterval(statusCheckInterval);
location.reload();
return;
}
}
} catch (error) {
console.error('Error in status check:', error);
}
// Also check DOM for processing badges
const currentBadges = document.querySelectorAll('.badge'); const currentBadges = document.querySelectorAll('.badge');
let stillProcessing = false; let stillProcessing = false;
@@ -659,11 +730,22 @@ document.addEventListener('DOMContentLoaded', function() {
} }
}); });
if (stillProcessing) { if (!stillProcessing) {
console.log('Still have processing files, refreshing page...'); console.log('No processing badges found, reloading page...');
clearInterval(statusCheckInterval);
location.reload(); location.reload();
} }
}, 10000); // Check every 10 seconds }, 3000); // Check every 3 seconds
// Also add a manual refresh reminder
setTimeout(() => {
const processingBadges = Array.from(document.querySelectorAll('.badge')).filter(badge =>
badge.textContent && badge.textContent.includes('Processing')
);
if (processingBadges.length > 0) {
console.log('Files still processing after 10 seconds, you can click refresh manually');
}
}, 10000);
} }
}); });
</script> </script>