您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Generate a summary of any webpage, selected text, YouTube video transcript using a local instance of g4f
当前为
// ==UserScript== // @name GPT4Free Page Summarizer (Local Free API) // @version 1.6 // @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() { 'use strict'; let loading = false; function summarizePage(textToSummarize, language, maxWords, callback) { const apiUrl = 'http://localhost:1337/v1/chat/completions'; const prompt = `Summarize the following text in ${language} using max ${maxWords} words. Summary is organised in paragraphs. Each paragraph is preceded by a title (inside by <b></b>). Each paragraph is it's followed by a <br> at the end of pagragraph (never at the beginning). Before each title (in the same line of the title) there si an emoji contextual to the paragraph topic. Don't add any other additional blank space or blank newline before or after each paragraph. 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) { let statusColor = '#00ff00'; // Green for success (2xx) if (response.status < 200 || response.status >= 300) { statusColor = '#ffcc00'; // Yellow for non-2xx } callback(null, response.responseText, response.status, statusColor); loading = false; updateButtonState(); updateButtonText(); }, onerror: function(error) { callback('Network error during the API call: ' + error, null, null, '#ff4444'); // Red for error loading = false; updateButtonState(); updateButtonText(); } }); loading = true; updateButtonState(); updateButtonText(); } 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 }); } // 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); updateButtonText(); // Update button text when transcript is available }) .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'); 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 word number selector const wordsSelector = document.createElement('select'); wordsSelector.style.backgroundColor = '#333333'; wordsSelector.style.color = '#ffffff'; wordsSelector.style.border = 'none'; wordsSelector.style.padding = '10px'; wordsSelector.style.fontSize = '14px'; wordsSelector.style.cursor = 'pointer'; const wordOptions = [50, 100, 200, 300]; wordOptions.forEach(words => { const option = document.createElement('option'); option.value = words; option.textContent = `${words} words`; if (words === 50) option.selected = true; wordsSelector.appendChild(option); }); buttonContainer.appendChild(wordsSelector); // Create the API status display const statusDisplay = document.createElement('div'); statusDisplay.style.fontSize = '12px'; 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 and loading state function updateButtonText() { if (loading) { summarizeButton.textContent = 'Loading..'; return; } const selectedText = window.getSelection().toString(); 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; } } // Function to update the status display with colored language and response status function updateStatusDisplay(statusText, responseColor) { statusDisplay.innerHTML = `Status: <span style="color: ${responseColor};">${statusText}</span> | Lang: <span style="color: #00bfff;">${selectedLanguage}</span>`; } // Function to update the button's disabled state function updateButtonState() { summarizeButton.disabled = loading; } // 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 updateStatusDisplay('Idle', '#888888'); summarizeButton.addEventListener('click', function() { if (loading) { return; // Do nothing if already loading } updateButtonText(); // Immediately set button to "Loading.." updateButtonState(); updateStatusDisplay('Requesting..', '#888888'); 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 = window.getSelection().toString(); console.log("Summarizing selected text.."); } else { textToSummarize = document.body.innerText; console.log("Summarizing page text.."); } const maxWords = parseInt(wordsSelector.value, 10); summarizePage(textToSummarize, selectedLanguage, maxWords, function(error, summaryText, statusCode, statusColor) { if (error) { summaryContainer.textContent = 'Error: ' + error; summaryContainer.style.color = '#ff4444'; updateStatusDisplay(`Failed${statusCode ? ` (${statusCode})` : ''}`, '#ff4444'); } else { try { const jsonResponse = JSON.parse(summaryText); if (jsonResponse.choices && jsonResponse.choices[0].message && jsonResponse.choices[0].message.content) { summaryContainer.innerHTML = jsonResponse.choices[0].message.content; summaryContainer.style.color = '#ffffff'; updateStatusDisplay(`Success (${statusCode})`, statusColor); } else { summaryContainer.textContent = 'Error: Formato della risposta API inatteso.'; summaryContainer.style.color = '#ff4444'; updateStatusDisplay(`Failed (${statusCode})`, '#ff4444'); } } catch (parseError) { summaryContainer.textContent = 'Error parsing JSON: ' + parseError; summaryContainer.style.color = '#ff4444'; updateStatusDisplay(`Failed (${statusCode})`, '#ff4444'); } } summaryContainer.style.display = 'block'; }); }); // Initial button text update updateButtonText(); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址