diff --git a/popup.js b/popup.js index 6326203..d975ab6 100644 --- a/popup.js +++ b/popup.js @@ -11,6 +11,50 @@ function debounce(func, wait) { }; } +// ... rest of your existing code ... + +function displayError(errorMessage) { + const resultContainer = document.getElementById('resultContainer'); + const summaryContainer = document.getElementById('summaryContainer'); + const resultDiv = document.getElementById('resultDiv'); + const copyButton = document.getElementById('copyButton'); + const summaryHeader = document.querySelector('.summary-header'); + + // Hide summary-related elements + if (summaryContainer) summaryContainer.style.display = 'none'; + if (copyButton) copyButton.style.display = 'none'; + if (summaryHeader) summaryHeader.style.display = 'none'; + + // Clear previous content + if (resultDiv) resultDiv.innerHTML = ''; + + const errorDiv = document.createElement('div'); + errorDiv.className = 'error-message user-friendly'; + errorDiv.innerHTML = ` +
${errorMessage}
+ `; + + if (resultDiv) resultDiv.appendChild(errorDiv); + if (resultContainer) resultContainer.style.display = 'block'; + + // Only add the "Open Settings" button if it's the API key error + if (errorMessage.includes('Enter your API key')) { + const openSettingsBtn = document.createElement('button'); + openSettingsBtn.id = 'openSettingsBtn'; + openSettingsBtn.className = 'action-button'; + openSettingsBtn.textContent = 'Open Settings'; + openSettingsBtn.addEventListener('click', function() { + const settingsModal = document.getElementById('settingsModal'); + if (settingsModal) { + settingsModal.style.display = 'block'; + loadApiKeysIntoSettingsForm(); + } + }); + errorDiv.appendChild(openSettingsBtn); + } +} + document.addEventListener('DOMContentLoaded', function() { const inputText = document.getElementById('inputText'); const modelSelect = document.getElementById('modelSelect'); @@ -24,12 +68,29 @@ document.addEventListener('DOMContentLoaded', function() { const copyButton = document.getElementById('copyButton'); const ttsButton = document.getElementById('ttsButton'); const ttsVoiceSelect = document.getElementById('ttsVoiceSelect'); + const exportButton = document.getElementById('exportButton'); let lastInput = ''; let lastModel = ''; let currentAudio = null; let preloadedAudio = null; let isPreloading = false; let ttsEndpointUrl = 'https://summariser-test-f7ab30f38c51.herokuapp.com/tts'; // Default URL + let currentActivityId = null; + let activityData = {}; + let lastActivityType = null; + let lastActivityTime = 0; + let currentSessionId = null; + + fetchTtsEndpointUrl(); + +// Update the export button event listener +if (exportButton) { + exportButton.addEventListener('click', debounce(function() { + exportSummary(); + updateActivity('summary_exported'); + }, 300)); // 300ms debounce time +} + function showToast(message) { let toast = document.getElementById('toast'); @@ -56,6 +117,14 @@ document.addEventListener('DOMContentLoaded', function() { }); } + function openSettingsModal() { + const settingsModal = document.getElementById('settingsModal'); + if (settingsModal) { + settingsModal.style.display = 'block'; + loadApiKeysIntoSettingsForm(); + } + } + async function fetchUrlContent(url) { try { const response = await fetch(url); @@ -119,19 +188,19 @@ document.addEventListener('DOMContentLoaded', function() { async function getUserId() { return new Promise((resolve) => { - chrome.storage.local.get(['userId'], function(result) { - if (result.userId) { - resolve(result.userId); + chrome.storage.local.get(['user_id'], function(result) { + if (result.user_id) { + resolve(result.user_id); } else { const newUserId = generateUUID(); - chrome.storage.local.set({userId: newUserId}, function() { + chrome.storage.local.set({ user_id: newUserId }, function() { resolve(newUserId); }); } }); }); } - + function generateUUID() { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); @@ -197,138 +266,217 @@ document.addEventListener('DOMContentLoaded', function() { return true; } - // Modify the event listener for the summarize button (around line 179) if (summarizeBtn) { - summarizeBtn.addEventListener('click', debounce(async function() { - const input = inputText.value.trim(); - const model = modelSelect.value; + summarizeBtn.addEventListener('click', async function(event) { + event.preventDefault(); + if (await checkApiKeyAndOpenSettings()) { + debounce(summarize, 300)(); + } + }); + } - if (!input) { - displayError('Please enter a URL or paste privacy policy text.'); + async function summarize() { + try { + if (!(await checkApiKeyAndOpenSettings())) { + return; + } + const inputText = document.getElementById('inputText').value.trim(); + const model = document.getElementById('modelSelect').value; + const userId = await getUserId(); + let token = await getValidToken(); // Changed from const to let + const startTime = Date.now(); + + if (!inputText) { + displayError('Please enter some text or a URL to summarize.'); return; } - - // Add this near the beginning of the click event listener, after getting the input - const maxInputLength = 50000; // Adjust this value as needed - if (input.length > maxInputLength) { + + const maxInputLength = 50000; + if (inputText.length > maxInputLength) { displayError(`Input is too long. Please limit your input to ${maxInputLength} characters.`); return; } - - // Replace lines 190-192 with this improved URL detection logic + const urlRegex = /https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_\+.~#?&//=]*)/gi; - const urlMatches = input.match(urlRegex); - + const urlMatches = inputText.match(urlRegex); + if (urlMatches && urlMatches.length > 1) { displayError('Please enter only one URL or paste the privacy policy text directly. Multiple URLs are not supported.'); return; } - + + showLoader(); + let isUrl = false; + let scrapedContent = null; + if (urlMatches && urlMatches.length === 1) { - // Single URL detected - let url = urlMatches[0]; - inputText.value = url; // Update the input field with the detected URL - } else if (urlMatches && urlMatches.length === 0) { - // No URL detected, check if the input contains "http" or "https" - if (input.includes('http://') || input.includes('https://')) { - displayError('Invalid URL format. Please enter a valid URL or paste the privacy policy text directly.'); + isUrl = true; + const url = urlMatches[0]; + try { + scrapedContent = await scrapeWebsite(url); + } catch (error) { + displayError(`Failed to scrape website: ${error.message}`); return; } + } else if (inputText.includes('http://') || inputText.includes('https://')) { + displayError('Invalid URL format. Please enter a valid URL or paste the privacy policy text directly.'); + return; } - - if (!isInputValid(input)) { + + if (!isInputValid(inputText)) { return; // The error message is already displayed by isInputValid } - - summarizeBtn.disabled = true; - showLoader(); - - try { - const freeSummariesLeft = await getFreeSummariesCount(); - let apiKey = null; - - if (freeSummariesLeft > 0) { - // Use server-side API key - apiKey = 'server_side'; + + const freeSummariesLeft = await getFreeSummariesCount(); + let apiKey = await getApiKey(`${model}ApiKey`); + + if (freeSummariesLeft <= 0 && !apiKey) { + throw new Error('No free summaries left and no valid API key provided'); + } + + const useServerSideKey = freeSummariesLeft > 0; + + const response = await fetch('https://summariser-test-f7ab30f38c51.herokuapp.com/summarize', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}` + }, + body: JSON.stringify({ + input: inputText, + model: model, + user_id: userId, + is_url: isUrl, + searched_data: inputText, + scrape_data: scrapedContent, + api_key: useServerSideKey ? 'server_side' : apiKey + }) + }); + + if (response.status === 403) { + const errorData = await response.json(); + if (errorData.error === "No free summaries left") { + throw new Error('No free summaries left'); } else { - // Use client-side API key - apiKey = await getApiKey(`${model}ApiKey`); - if (!apiKey) { - throw new Error('No free summaries left and no valid API key provided'); + console.log('Received 403 error, attempting to refresh token'); + token = await requestNewToken(); + if (!token) { + throw new Error('Failed to refresh authentication token'); } + // Retry the request with the new token + return await summarize(); } - - const response = await fetch('https://summariser-test-f7ab30f38c51.herokuapp.com/summarize', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - input: input, - model: model, - api_key: apiKey, - user_id: await getUserId() - }) - }); - - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - - const result = await response.json(); - - if (result.error) { - throw new Error(result.error); - } - - displaySummary(result.summary); - updateFreeSummariesDisplay(result.free_summaries_left); - } catch (error) { - if (error.message.includes('No free summaries left')) { - displayError('You have used all your free summaries. Enter your API key(s) to continue.'); - } else { - displayError(`Failed to summarize: ${error.message}`); - } - } finally { - summarizeBtn.disabled = false; - hideLoader(); } - }, 300)); + + if (!response.ok) { + const errorData = await response.json(); + throw new Error(errorData.message || `HTTP error! status: ${response.status}`); + } + + const result = await response.json(); + + if (result.error) { + throw new Error(result.error); + } + + displaySummary(result.summary, model); + updateFreeSummariesDisplay(result.free_summaries_left); + currentSessionId = result.session_id; // Store the session_id + + // Start a new session here + startNewSession({ + model: model, + isUrl: isUrl, + input: inputText, + scrapeData: scrapedContent + }); + + const activityData = { + model_selected: model, + searched_for: isUrl ? 'url' : 'text', + searched_data: inputText, + scrape_data: scrapedContent, + request_time: (Date.now() - startTime) / 1000, // Convert to seconds + tts_used: false, + copied_summary: false, + summary_exported: false + }; + // Reset activity flags + lastActivityType = null; + lastActivityTime = 0; + await sendUserActivity(activityData); + + } catch (error) { + if (error.message === 'No free summaries left and no valid API key provided') { + displayError('You have used all your free summaries. Please enter your API key to continue.'); + openSettingsModal(); + } else { + displayError(`Failed to summarize: ${error.message}`); + } + } finally { + hideLoader(); + } + } + async function checkApiKeyAndOpenSettings() { + const model = document.getElementById('modelSelect').value; + const freeSummariesLeft = await getFreeSummariesCount(); + const apiKey = await getApiKey(`${model}ApiKey`); + + console.log('Model:', model); + console.log('Free summaries left:', freeSummariesLeft); + console.log('API Key exists:', !!apiKey); + + if (freeSummariesLeft <= 0 && !apiKey) { + displayError('You have used all your free summaries. Please enter your API key to continue.'); + openSettingsModal(); + return false; + } + return true; } async function scrapeWebsite(url) { try { + const token = await getValidToken(); const response = await fetch('https://summariser-test-f7ab30f38c51.herokuapp.com/scrape', { method: 'POST', headers: { 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}` }, - body: JSON.stringify({ url }) + body: JSON.stringify({ url: url }) }); - + if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } - + const data = await response.json(); return data.content; } catch (error) { - throw new Error('Failed to scrape website content. Please try pasting the text directly.'); + console.error('Error scraping website:', error); + throw error; } } - // Add this function to get the current free summaries count async function getFreeSummariesCount() { try { + const token = await getValidToken(); const response = await fetch('https://summariser-test-f7ab30f38c51.herokuapp.com/get_free_summaries_count', { method: 'POST', headers: { 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}` }, body: JSON.stringify({ user_id: await getUserId() }) }); + if (response.status === 403) { + return 0; // No free summaries left + } + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } const data = await response.json(); return data.free_summaries_left !== undefined ? data.free_summaries_left : 0; } catch (error) { @@ -462,48 +610,6 @@ document.addEventListener('DOMContentLoaded', function() { showToast("Preparing audio. It will be available soon."); preloadAudioWithCurrentSettings(); } - - function displayError(errorMessage) { - const resultContainer = document.getElementById('resultContainer'); - const summaryContainer = document.getElementById('summaryContainer'); - const resultDiv = document.getElementById('resultDiv'); - const copyButton = document.getElementById('copyButton'); - const summaryHeader = document.querySelector('.summary-header'); - - // Hide summary-related elements - summaryContainer.style.display = 'none'; - copyButton.style.display = 'none'; - summaryHeader.style.display = 'none'; - - // Clear previous content - resultDiv.innerHTML = ''; - - const errorDiv = document.createElement('div'); - errorDiv.className = 'error-message user-friendly'; - errorDiv.innerHTML = ` -${errorMessage}
- `; - - resultDiv.appendChild(errorDiv); - resultContainer.style.display = 'block'; - - // Only add the "Open Settings" button if it's the API key error - if (errorMessage.includes('Enter your API key')) { - const openSettingsBtn = document.createElement('button'); - openSettingsBtn.id = 'openSettingsBtn'; - openSettingsBtn.className = 'action-button'; - openSettingsBtn.textContent = 'Open Settings'; - openSettingsBtn.addEventListener('click', function() { - if (settingsModal) { - settingsModal.style.display = 'block'; - loadApiKeysIntoSettingsForm(); - } - }); - errorDiv.appendChild(openSettingsBtn); - } - } - function updateFreeSummariesDisplay(count) { const counterElement = document.getElementById('freeSummariesCounter'); @@ -514,30 +620,31 @@ document.addEventListener('DOMContentLoaded', function() { } } - // Add this function to fetch the free summaries count async function fetchFreeSummariesCount() { try { const userId = await getUserId(); - console.log('Fetching free summaries count for user:', userId); + const token = await getValidToken(); + // console.log('Fetching free summaries count for user:', userId); const response = await fetch('https://summariser-test-f7ab30f38c51.herokuapp.com/get_free_summaries_count', { method: 'POST', headers: { 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}` }, body: JSON.stringify({ user_id: userId }) }); const data = await response.json(); - console.log('Received data from server:', data); + // console.log('Received data from server:', data); if (data.free_summaries_left !== undefined) { updateFreeSummariesDisplay(data.free_summaries_left); if (data.new_user) { showToast("Welcome! You've been granted 10 free summaries."); - console.log('New user detected, granted 10 free summaries'); + // console.log('New user detected, granted 10 free summaries'); } } else { - console.log('Free summaries count not found in response'); + // console.log('Free summaries count not found in response'); updateFreeSummariesDisplay(0); } } catch (error) { @@ -574,16 +681,24 @@ document.addEventListener('DOMContentLoaded', function() { // Call this function when the popup is loaded updateModelSelectOptions(); - // Move the copyButton event listener inside the DOMContentLoaded event + // Add this function to handle the copy button if (copyButton) { - copyButton.addEventListener('click', function() { - const summaryContent = document.getElementById('summaryContainer').innerText; - navigator.clipboard.writeText(summaryContent).then(function() { - showToast("Summary copied to clipboard!"); - }).catch(function(err) { - showToast("Failed to copy summary. Please try again."); - }); - }); + copyButton.addEventListener('click', debounce(function() { + const summaryContainer = document.getElementById('summaryContainer'); + if (summaryContainer) { + const summaryText = summaryContainer.innerText; + navigator.clipboard.writeText(summaryText).then(() => { + showToast("Summary copied to clipboard!"); + updateActivity('copied_summary'); + }).catch(err => { + console.error('Failed to copy summary: ', err); + showToast("Failed to copy summary. Please try again."); + }); + } else { + console.error('Summary container not found'); + showToast("Error: Summary not available"); + } + }, 300)); // 300ms debounce time } // Add this function to load API keys into the settings form @@ -593,7 +708,7 @@ document.addEventListener('DOMContentLoaded', function() { const claudeInput = document.getElementById('claudeApiKeyInput'); const geminiInput = document.getElementById('geminiApiKeyInput'); const mistralInput = document.getElementById('mistralApiKeyInput'); - + if (gpt4oMiniInput) gpt4oMiniInput.value = result['gpt-4o-miniApiKey'] || ''; if (claudeInput) claudeInput.value = result['claude-3-5-sonnet-20240620ApiKey'] || ''; if (geminiInput) geminiInput.value = result['gemini-1.5-flash-8bApiKey'] || ''; @@ -690,7 +805,6 @@ document.addEventListener('DOMContentLoaded', function() { standardizedResponse = standardizedResponse.replace(/^\*\*(.*?)\*\*$/gm, (match, content) => { return `