GPT4Free Page Summarizer (Local Free API)

Generate a summary of any webpage or selected text using a local instance of g4f

目前為 2025-04-11 提交的版本,檢視 最新版本

// ==UserScript==
// @name         GPT4Free Page Summarizer (Local Free API)
// @namespace    http://tampermonkey.net/
// @version      1.3
// @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 = '999999'; // Increased to ensure it's above all elements
    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 = '#FFC107'; // Mustard yellow
    toggleButton.style.color = '#000000'; // White text
    toggleButton.style.border = 'none';
    toggleButton.style.padding = '10px';
    toggleButton.style.cursor = 'pointer';
    toggleButton.style.zIndex = '1000000'; // Higher than sidebar
    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';
    summarizeButton.style.color = '#ffffff';
    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';
    summarizeButton.onmouseout = () => summarizeButton.style.backgroundColor = '#333333';
    buttonContainer.appendChild(summarizeButton);

    // Create the line number selector
    const linesSelector = document.createElement('select');
    linesSelector.style.backgroundColor = '#333333';
    linesSelector.style.color = '#ffffff';
    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', () => {
        setTimeout(updateButtonText, 100);
    });

    // 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();
            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';
        });
    });
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址