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-14 提交的版本,檢視 最新版本

// ==UserScript==
// @name         GPT4Free Page Summarizer (Local Free API)
// @version      1.4.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.
                                 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 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';

        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.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或关注我们的公众号极客氢云获取最新地址