// Index page functionality document.addEventListener('DOMContentLoaded', () => { const youtubeUrlInput = document.getElementById('youtube-url'); const processButton = document.getElementById('process-button'); const processStatus = document.getElementById('process-status'); const processingIndicator = document.getElementById('processing'); const recentlyProcessedCard = document.getElementById('recently-processed'); const videoListContainer = document.getElementById('video-list'); // Check for search parameter in URL const urlParams = new URLSearchParams(window.location.search); const searchQuery = urlParams.get('search'); if (searchQuery) { // Display search results displaySearchResults(searchQuery); } // Example video buttons const exampleButtons = document.querySelectorAll('.example-video'); // Process button click handler processButton.addEventListener('click', () => processVideo()); // Enter key in input field youtubeUrlInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') processVideo(); }); // Example video buttons exampleButtons.forEach(button => { button.addEventListener('click', () => { youtubeUrlInput.value = button.dataset.url; processVideo(); }); }); // Process video function function processVideo() { const youtubeUrl = youtubeUrlInput.value.trim(); if (!youtubeUrl) { processStatus.innerHTML = '
Please enter a YouTube URL
'; return; } // Extract video ID const videoId = extractVideoId(youtubeUrl); if (!videoId) { processStatus.innerHTML = '
Invalid YouTube URL
'; return; } // Show loading indicator with spinner and text processStatus.innerHTML = `
Processing video... This may take a few moments
`; // Set a timeout to handle overly long processing const timeoutId = setTimeout(() => { processStatus.innerHTML = `
Processing is taking longer than expected. Please wait...
`; }, 20000); // 20 seconds // Send request to process the video fetch('/api/video/process', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ url: youtubeUrl }) }) .then(response => { if (!response.ok) { throw new Error('Failed to process video'); } return response.json(); }) .then(data => { // Clear timeout for long-running process clearTimeout(timeoutId); // Extract video ID from response (handles both old and new API formats) const videoId = data.video ? data.video.video_id : data.video_id; const isNewlyProcessed = data.newly_processed !== undefined ? data.newly_processed : true; if (!videoId) { throw new Error('Invalid response: Missing video ID'); } // Get video title (for display) const videoTitle = data.video ? data.video.title : (data.title || `Video ${videoId}`); // Log for debugging console.log('Process response:', {videoId, isNewlyProcessed, data}); // Show success message processStatus.innerHTML = ` `; // Update recent videos lists displayRecentVideos(); loadFooterRecentVideos(); // Update footer videos as well }) .catch(error => { // Clear timeout for long-running process clearTimeout(timeoutId); // Show error message console.error('Process error:', error); processStatus.innerHTML = handleError(error); }); } // Display recently processed videos function displayRecentVideos() { // Show loading state recentlyProcessedCard.classList.remove('hidden'); videoListContainer.innerHTML = `
Loading recent videos...
`; const carouselPrev = document.getElementById('carousel-prev'); const carouselNext = document.getElementById('carousel-next'); // Fetch recent videos from server fetch('/api/video/recent?limit=5') .then(response => { if (!response.ok) { throw new Error('Failed to fetch recent videos'); } return response.json(); }) .then(videos => { if (videos && videos.length > 0) { // Limit to 5 videos const limitedVideos = videos.slice(0, 5); // Generate carousel items const carouselItems = limitedVideos.map((video, index) => { // Format date if available let formattedDate = ''; if (video.created_at) { const date = new Date(video.created_at * 1000); // Convert Unix timestamp to milliseconds formattedDate = date.toLocaleDateString(); } // Use title or default const videoTitle = video.title || `Video ${video.video_id}`; return ` `; }).join(''); // Add carousel items to container videoListContainer.innerHTML = carouselItems; // Setup navigation arrows if (limitedVideos.length > 1) { // Show arrows for multiple videos let currentIndex = 0; const maxIndex = limitedVideos.length - 1; // Show navigation arrows carouselPrev.classList.remove('hidden'); carouselNext.classList.remove('hidden'); // Left button is disabled by default (we're at the start) const prevButton = carouselPrev.querySelector('button'); const nextButton = carouselNext.querySelector('button'); prevButton.classList.add('btn-disabled'); // Functions to update button states const updateButtonStates = () => { if (currentIndex === 0) { prevButton.classList.add('btn-disabled'); } else { prevButton.classList.remove('btn-disabled'); } if (currentIndex === maxIndex) { nextButton.classList.add('btn-disabled'); } else { nextButton.classList.remove('btn-disabled'); } }; // Setup navigation buttons prevButton.addEventListener('click', () => { if (currentIndex > 0) { currentIndex--; document.getElementById(`video-${currentIndex}`).scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' }); updateButtonStates(); } }); nextButton.addEventListener('click', () => { if (currentIndex < maxIndex) { currentIndex++; document.getElementById(`video-${currentIndex}`).scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' }); updateButtonStates(); } }); } else { // Hide arrows for single video carouselPrev.classList.add('hidden'); carouselNext.classList.add('hidden'); } } else { recentlyProcessedCard.classList.add('hidden'); carouselPrev.classList.add('hidden'); carouselNext.classList.add('hidden'); } }) .catch(error => { console.error('Error fetching recent videos:', error); videoListContainer.innerHTML = `
Failed to load recent videos
`; carouselPrev.classList.add('hidden'); carouselNext.classList.add('hidden'); }); } // Display recent videos on page load displayRecentVideos(); // Function to display search results function displaySearchResults(query) { // Try to use the modal if available const searchModal = document.getElementById('search-results-modal'); const searchResultsContainer = document.getElementById('search-results-container'); if (searchModal && searchResultsContainer) { // Update modal title with search term const modalTitle = searchModal.querySelector('h3'); if (modalTitle) { modalTitle.textContent = `Search Results for "${query}"`; } // Show the modal searchModal.showModal(); // Clear previous results and show loading state searchResultsContainer.innerHTML = `
Searching...
`; // Fetch and display results in the modal fetchAndDisplayResults(query, searchResultsContainer); } else { // Fallback to the old implementation if modal is not available // Show a search results card with loading indicator processStatus.innerHTML = `

Search Results for "${query}"

Searching...
`; // Fetch and display results in the processStatus element fetchAndDisplayResults(query, null, processStatus); } } // Helper function to fetch and display search results function fetchAndDisplayResults(query, container, fallbackContainer) { // Fetch search results from API fetch(`/api/video/search?query=${encodeURIComponent(query)}&limit=10`) .then(response => { if (!response.ok) { throw new Error('Failed to perform search'); } return response.json(); }) .then(results => { if (results && results.length > 0) { // Group results by video const videoGroups = {}; // First pass: collect all video IDs that need titles const videoIds = []; results.forEach(result => { const videoId = result.segment.video_id; if (!videoGroups[videoId]) { videoGroups[videoId] = { videoId: videoId, title: `Video ${videoId}`, // Default title, will be updated segments: [] }; // Add to list of IDs to fetch titles for videoIds.push(videoId); } videoGroups[videoId].segments.push(result); }); // If we have video IDs, fetch their proper titles const videoPromises = videoIds.map(videoId => { return fetch(`/api/video/info/${videoId}`) .then(response => response.ok ? response.json() : null) .then(videoInfo => { if (videoInfo && videoInfo.title && videoGroups[videoId]) { videoGroups[videoId].title = videoInfo.title; } }) .catch(error => console.error(`Error fetching video info for ${videoId}:`, error)); }); // After all video titles are fetched, continue with rendering Promise.all(videoPromises).then(() => { // Generate results HTML const resultsHTML = Object.values(videoGroups).map(group => { const segmentsHTML = group.segments.map(result => { return `
${formatTime(result.segment.start)}

${result.segment.text}

Score: ${(result.score * 100).toFixed(1)}%
`; }).join(''); return `
Thumbnail

${group.title}

${segmentsHTML}
`; }).join(''); // Display results in the appropriate container if (container) { container.innerHTML = resultsHTML; } else if (fallbackContainer) { fallbackContainer.innerHTML = `

Search Results for "${query}"

${resultsHTML}
`; } }); } else { // No results found - display immediately, no need to wait for titles const noResultsHTML = `
No results found. Try a different search term or process more videos.
`; if (container) { container.innerHTML = noResultsHTML; } else if (fallbackContainer) { fallbackContainer.innerHTML = `

Search Results for "${query}"

${noResultsHTML}
`; } } }) .catch(error => { console.error('Search error:', error); const errorHTML = `
Error performing search: ${error.message}
`; if (container) { container.innerHTML = errorHTML; } else if (fallbackContainer) { fallbackContainer.innerHTML = `

Search Results for "${query}"

${errorHTML}
`; } }); } });