// ==UserScript==
// @name GPT4Free Page Summarizer (Local Free API)
// @namespace http://tampermonkey.net/
// @version 1.2
// @description Generate a summary of any webpage or selected text using a local instance of g4f
// @author SH3LL
// @match *://*/*
// @grant GM.xmlHttpRequest
// ==/UserScript==
function getPageText() {
return document.body.innerText;
}
function getSelectedText() {
return window.getSelection().toString();
}
function summarizePage(textToSummarize, language, maxLines, callback) {
const apiUrl = 'http://localhost:1337/v1/chat/completions';
const prompt = `Summarize the following text in ${language} in max ${maxLines} lines, in a clear, concise and text organised in paragraphs.
Don't add any other sentence like "Here is the summary", write directly the summary itself.
Here is the text: ${textToSummarize}`;
const data = {
messages: [{
role: 'user',
content: prompt
}],
model: 'DeepSeek-V3',
provider: 'Blackbox'
};
GM.xmlHttpRequest({
method: 'POST',
url: apiUrl,
headers: {
'Content-Type': 'application/json'
},
data: JSON.stringify(data),
onload: function(response) {
if (response.status >= 200 && response.status < 300) {
try {
const jsonResponse = JSON.parse(response.responseText);
if (jsonResponse.choices && jsonResponse.choices[0].message && jsonResponse.choices[0].message.content) {
callback(null, jsonResponse.choices[0].message.content, response.status);
} else {
callback('Unexpected format of the API response.', null, response.status);
}
} catch (error) {
callback('Error parsing the API response: ' + error, null, response.status);
}
} else {
callback('Error in the API call: ' + response.status + ' ' + response.statusText, null, response.status);
}
},
onerror: function(error) {
callback('Network error during the API call: ' + error, null, null);
}
});
}
(function() {
'use strict';
const languageNames = new Intl.DisplayNames(['it'], { type: 'language' });
const browserLanguage = navigator.language;
const selectedLanguage = languageNames.of(browserLanguage);
// Create a container for the Shadow DOM
const shadowHost = document.createElement('div');
document.body.appendChild(shadowHost);
const shadowRoot = shadowHost.attachShadow({ mode: 'open' });
// Create the sidebar within the Shadow DOM
const sidebar = document.createElement('div');
sidebar.style.position = 'fixed';
sidebar.style.right = '-300px';
sidebar.style.top = '0';
sidebar.style.width = '300px';
sidebar.style.height = '100vh';
sidebar.style.backgroundColor = '#000000';
sidebar.style.color = '#ffffff';
sidebar.style.padding = '20px';
sidebar.style.zIndex = '9999';
sidebar.style.fontFamily = 'Arial, sans-serif';
sidebar.style.boxSizing = 'border-box';
sidebar.style.display = 'flex';
sidebar.style.flexDirection = 'column';
sidebar.style.gap = '10px';
sidebar.style.transition = 'right 0.3s ease';
shadowRoot.appendChild(sidebar);
// Create the button to show/hide the sidebar
const toggleButton = document.createElement('button');
toggleButton.textContent = '>';
toggleButton.style.position = 'fixed';
toggleButton.style.right = '0';
toggleButton.style.top = '20px';
toggleButton.style.backgroundColor = '#333333'; // Dark gray
toggleButton.style.color = '#ffffff'; // White text
toggleButton.style.border = 'none';
toggleButton.style.padding = '10px';
toggleButton.style.cursor = 'pointer';
toggleButton.style.zIndex = '10000';
toggleButton.style.fontSize = '14px';
toggleButton.style.transition = 'right 0.3s ease';
shadowRoot.appendChild(toggleButton);
// Create the container for the button and selector
const buttonContainer = document.createElement('div');
buttonContainer.style.display = 'flex';
buttonContainer.style.gap = '10px';
buttonContainer.style.alignItems = 'center';
sidebar.appendChild(buttonContainer);
// Create the summarize button
const summarizeButton = document.createElement('button');
summarizeButton.textContent = 'Summarize';
summarizeButton.style.backgroundColor = '#333333'; // Dark gray
summarizeButton.style.color = '#ffffff'; // White text
summarizeButton.style.border = 'none';
summarizeButton.style.padding = '10px 20px';
summarizeButton.style.cursor = 'pointer';
summarizeButton.style.fontSize = '14px';
summarizeButton.style.flex = '1';
summarizeButton.style.transition = 'background-color 0.3s';
summarizeButton.onmouseover = () => summarizeButton.style.backgroundColor = '#4d4d4d'; // Lighter shade on hover
summarizeButton.onmouseout = () => summarizeButton.style.backgroundColor = '#333333'; // Return to dark gray
buttonContainer.appendChild(summarizeButton);
// Create the line number selector
const linesSelector = document.createElement('select');
linesSelector.style.backgroundColor = '#333333'; // Dark gray
linesSelector.style.color = '#ffffff'; // White text
linesSelector.style.border = 'none';
linesSelector.style.padding = '10px';
linesSelector.style.fontSize = '14px';
linesSelector.style.cursor = 'pointer';
const lineOptions = [5, 10, 15, 20, 25, 30, 50, 100];
lineOptions.forEach(lines => {
const option = document.createElement('option');
option.value = lines;
option.textContent = `${lines} lines`;
if (lines === 5) option.selected = true;
linesSelector.appendChild(option);
});
buttonContainer.appendChild(linesSelector);
// Create the API status display with detected language
const statusDisplay = document.createElement('div');
statusDisplay.style.fontSize = '12px';
statusDisplay.style.color = '#888888';
statusDisplay.textContent = `Status: Idle | Lang: ${selectedLanguage}`;
sidebar.appendChild(statusDisplay);
// Create the summary container
const summaryContainer = document.createElement('div');
summaryContainer.style.fontSize = '14px';
summaryContainer.style.lineHeight = '1.5';
summaryContainer.style.display = 'none';
summaryContainer.style.overflowY = 'auto';
summaryContainer.style.maxHeight = 'calc(100vh - 130px)';
sidebar.appendChild(summaryContainer);
// Variable to track if text is selected
let isTextSelected = false;
// Function to update the button text based on text selection
function updateButtonText() {
const selectedText = getSelectedText();
if (selectedText) {
summarizeButton.textContent = `Summarize [${selectedText.substring(0, 2).trim().replace(/^\n+/, '').trim()}..]`;
summarizeButton.style.fontSize = "14px";
isTextSelected = true;
} else {
summarizeButton.textContent = 'Summarize';
summarizeButton.style.fontSize = "14px";
isTextSelected = false;
}
}
// Listen for text selection changes on the document
document.addEventListener('mouseup', updateButtonText);
document.addEventListener('mousedown', () => {
// Reset the button text if the user starts a new selection or clicks away
setTimeout(updateButtonText, 100); // Use a small delay to handle clicks
});
// Handle sidebar visibility
let isSidebarVisible = false;
toggleButton.addEventListener('click', function() {
if (isSidebarVisible) {
sidebar.style.right = '-300px';
toggleButton.style.right = '0';
toggleButton.textContent = '>';
} else {
sidebar.style.right = '0';
toggleButton.style.right = '300px';
toggleButton.textContent = '<';
}
isSidebarVisible = !isSidebarVisible;
});
// Listener for the summarize button
summarizeButton.addEventListener('click', function() {
summarizeButton.disabled = true;
summarizeButton.textContent = 'Loading...';
statusDisplay.textContent = `Status: Requesting... | Lang: ${selectedLanguage}`;
summaryContainer.style.display = 'none';
let textToSummarize = '';
if (isTextSelected) {
textToSummarize = getSelectedText();
} else {
textToSummarize = getPageText();
}
const maxLines = parseInt(linesSelector.value, 10);
summarizePage(textToSummarize, selectedLanguage, maxLines, function(error, summary, statusCode) {
summarizeButton.disabled = false;
updateButtonText(); // Reset button text after summarization
if (error) {
summaryContainer.textContent = 'Error: ' + error;
summaryContainer.style.color = '#ff4444';
statusDisplay.textContent = `Status: Failed${statusCode ? ` (${statusCode})` : ''} | Lang: ${selectedLanguage}`;
statusDisplay.style.color = '#ff4444';
} else {
summaryContainer.textContent = summary;
summaryContainer.style.color = '#ffffff';
statusDisplay.textContent = `Status: Success (${statusCode}) | Lang: ${selectedLanguage}`;
statusDisplay.style.color = '#00ff00';
}
summaryContainer.style.display = 'block';
});
});
})();