GPT4Free Page Summarizer (Local Free API)

Generate a summary of any webpage, selected text, YouTube video transcript using a local instance of g4f

目前为 2025-04-15 提交的版本。查看 最新版本

// ==UserScript==
// @name         GPT4Free Page Summarizer (Local Free API)
// @version      1.5.1
// @description  Generate a summary of any webpage, selected text, YouTube video transcript using a local instance of g4f
// @author       SH3LL
// @match        *://*/*
// @grant        GM.xmlHttpRequest
// @run-at       document-end
// @namespace http://tampermonkey.net/
// ==/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. Each paragraph
                    is logally separated by others by <br> and a title (titles are inside <b></b>).
                    Before each title (in the same line of the title) there si an emoji contextual to the paragraph topic.
                    Don't add any other sentence like "Here is the summary", write directly the summary itself.
                    Exclude from the summary any advertisement or sponsorization.
                    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 getYouTubeTranscript() {
    return new Promise((resolve, reject) => {
        const TRANSCRIPT_TARGET_ID = "engagement-panel-searchable-transcript";
        const VISIBILITY_EXPANDED = "ENGAGEMENT_PANEL_VISIBILITY_EXPANDED";
        const VISIBILITY_HIDDEN = "ENGAGEMENT_PANEL_VISIBILITY_HIDDEN";
        const TRANSCRIPT_CONTENT_SELECTOR = `ytd-engagement-panel-section-list-renderer[target-id="${TRANSCRIPT_TARGET_ID}"] #content`;
        const SEGMENT_SELECTOR = '.segment-text';

        let transcriptPanelElement = null;

        function hideTranscriptPanel() {
            if (transcriptPanelElement) {
                transcriptPanelElement.setAttribute("visibility", VISIBILITY_HIDDEN);
                transcriptPanelElement = null; // Release the reference
            }
        }

        function extractTranscript() {
            const transcriptPanelContent = document.querySelector(TRANSCRIPT_CONTENT_SELECTOR);
            if (transcriptPanelContent) {
                let transcriptText = "";
                transcriptPanelContent.querySelectorAll(SEGMENT_SELECTOR).forEach(segment => {
                    transcriptText += segment.textContent.trim() + " ";
                });
                hideTranscriptPanel();
                resolve(transcriptText.trim());
            } else {
                reject("Transcript panel content not found.");
            }
        }

        function onTranscriptPanelVisible() {
            const transcriptContentObserver = new MutationObserver((mutationsList, observer) => {
                const transcriptPanelContent = document.querySelector(TRANSCRIPT_CONTENT_SELECTOR);
                if (transcriptPanelContent && transcriptPanelContent.children.length > 0) {
                    extractTranscript();
                    observer.disconnect(); // Stop observing once extracted
                }
            });

            const transcriptPanel = document.querySelector(`ytd-engagement-panel-section-list-renderer[target-id="${TRANSCRIPT_TARGET_ID}"]`);
            const contentTarget = transcriptPanel ? transcriptPanel.querySelector('#content') : null;
            const config = { childList: true, subtree: true }; // Observe for addition of child nodes
            if (contentTarget) {
                transcriptContentObserver.observe(contentTarget, config);
            } else {
                reject("Could not find the #content element within the transcript panel.");
            }
        }

        function showTranscriptPanelAndObserveContent() {
            const transcripts = document.querySelectorAll(`[target-id="${TRANSCRIPT_TARGET_ID}"]`);
            if (transcripts.length === 1) {
                transcriptPanelElement = transcripts[0]; // Store panel element
                transcriptPanelElement.setAttribute("visibility", VISIBILITY_EXPANDED);
                onTranscriptPanelVisible();
            } else {
                reject('Transcript panel element not found.');
            }
        }

        const panelObserver = new MutationObserver((mutationsList, observer) => {
            const transcriptPanelContainer = document.querySelector(`ytd-engagement-panel-section-list-renderer[target-id="${TRANSCRIPT_TARGET_ID}"]`);
            if (transcriptPanelContainer) {
                showTranscriptPanelAndObserveContent();
                observer.disconnect(); // Stop observing once found
            }
        });

        // Start observing for the transcript panel container
        const targetNode = document.body;
        const config = { childList: true, subtree: true };
        panelObserver.observe(targetNode, config);

        // Set a timeout in case the transcript panel doesn't load
        setTimeout(() => {
            if (!transcriptPanelElement && !document.querySelector(TRANSCRIPT_CONTENT_SELECTOR)) {
                panelObserver.disconnect(); // Ensure observer is stopped
                reject("Timeout: Could not find transcript panel.");
            }
        }, 10000); // Adjust timeout as needed
    });
}

(function() {
    'use strict';

    // GET LANGUAGE PAGE
    const browserLanguage = navigator.language;
    let selectedLanguage;
    try {
        const languageNames = new Intl.DisplayNames([browserLanguage], { type: 'language' });
        selectedLanguage = languageNames.of(browserLanguage);
        // Fallback to the raw language code if Intl.DisplayNames fails or returns undefined
        if (!selectedLanguage) {
            selectedLanguage = browserLanguage;
        }
    } catch (error) {
        console.error("Error initializing Intl.DisplayNames:", error);
        selectedLanguage = browserLanguage; // Fallback to raw language code
    }

    // GET YOUTUBE TRANSCRIPT
    let ytTranscript = null;
    const isYouTubeVideoPage = window.location.hostname.includes("youtube.com") && window.location.pathname === "/watch";
    if (isYouTubeVideoPage) {
        getYouTubeTranscript()
            .then(transcript => {
                ytTranscript = transcript;
                console.log("YOUTUBE Transcript:", transcript);
            })
            .catch(error => {
                console.error("Error getting YOUTUBE transcript:", error);
            });
    }

    // CREATE DOM CONTAINER
    const shadowHost = document.createElement('div');
    document.body.appendChild(shadowHost);
    const shadowRoot = shadowHost.attachShadow({ mode: 'open' });

    // CREATE SIDEBAR 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 BUTTON HIDE/SHOW 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 CONTAINER FOR BUTTON AND SELECTOR
    const buttonContainer = document.createElement('div');
    buttonContainer.style.display = 'flex';
    buttonContainer.style.gap = '10px';
    buttonContainer.style.alignItems = 'center';
    sidebar.appendChild(buttonContainer);

    // CREATE SUMMARIZE BUTTON
    const summarizeButton = document.createElement('button');
    if (isYouTubeVideoPage && ytTranscript!==null) {
        summarizeButton.textContent = 'Summarize YT';
    }else{
        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 if (isYouTubeVideoPage && ytTranscript!==null) {
            summarizeButton.textContent = 'Summarize YT';
            summarizeButton.style.fontSize = "14px";
            isTextSelected = false;
        } 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.style.color = '#888888';
        statusDisplay.textContent = `Status: Requesting... | Lang: ${selectedLanguage}`;
        summaryContainer.style.display = 'none';
        summaryContainer.style.whiteSpace = 'pre-line';

        let textToSummarize = '';
        if (isYouTubeVideoPage && !isTextSelected && ytTranscript) {
            textToSummarize = ytTranscript;
            console.log("Summarizing YouTube transcript...");
        } else if (isTextSelected) {
            textToSummarize = getSelectedText();
            console.log("Summarizing selected text...");
        } else {
            textToSummarize = getPageText();
            console.log("Summarizing page text...");
        }

        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.innerHTML = summary;
                summaryContainer.style.color = '#ffffff';
                statusDisplay.textContent = `Status: Success (${statusCode}) | Lang: ${selectedLanguage}`;
                statusDisplay.style.color = '#00ff00';
            }
            summaryContainer.style.display = 'block';
        });
    });
})();

QingJ © 2025

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