Wikipedia AI Summarizer

Instantly summarize Wikipedia articles with AI-powered insights

Size

18.2 KB

Version

1.0.1

Created

Nov 6, 2025

Updated

about 1 month ago

1// ==UserScript==
2// @name		Wikipedia AI Summarizer
3// @description		Instantly summarize Wikipedia articles with AI-powered insights
4// @version		1.0.1
5// @match		https://*.en.wikipedia.org/*
6// @icon		https://en.wikipedia.org/static/favicon/wikipedia.ico
7// ==/UserScript==
8(function() {
9    'use strict';
10
11    console.log('Wikipedia AI Summarizer extension loaded');
12
13    // Debounce function to prevent excessive calls
14    function debounce(func, wait) {
15        let timeout;
16        return function executedFunction(...args) {
17            const later = () => {
18                clearTimeout(timeout);
19                func(...args);
20            };
21            clearTimeout(timeout);
22            timeout = setTimeout(later, wait);
23        };
24    }
25
26    // Function to extract article content
27    function getArticleContent() {
28        const contentDiv = document.querySelector('#mw-content-text .mw-parser-output');
29        if (!contentDiv) {
30            console.error('Could not find article content');
31            return null;
32        }
33
34        // Get all paragraphs, excluding references and other non-content sections
35        const paragraphs = Array.from(contentDiv.querySelectorAll('p'))
36            .filter(p => {
37                // Exclude paragraphs in reference sections, infoboxes, etc.
38                const parent = p.closest('.reflist, .navbox, .infobox, .metadata, .ambox');
39                return !parent && p.textContent.trim().length > 50;
40            })
41            .map(p => p.textContent.trim())
42            .slice(0, 15); // Take first 15 paragraphs to avoid token limits
43
44        const articleText = paragraphs.join('\n\n');
45        console.log('Extracted article content, length:', articleText.length);
46        return articleText;
47    }
48
49    // Function to get article title
50    function getArticleTitle() {
51        const titleElement = document.querySelector('h1#firstHeading .mw-page-title-main, h1.firstHeading');
52        return titleElement ? titleElement.textContent.trim() : 'this article';
53    }
54
55    // Function to show loading indicator
56    function showLoadingIndicator() {
57        const existingLoader = document.getElementById('wiki-ai-loader');
58        if (existingLoader) return;
59
60        const loader = document.createElement('div');
61        loader.id = 'wiki-ai-loader';
62        loader.innerHTML = `
63            <div style="display: flex; align-items: center; gap: 10px;">
64                <div class="spinner"></div>
65                <span>AI is analyzing the article...</span>
66            </div>
67        `;
68        document.body.appendChild(loader);
69        console.log('Loading indicator shown');
70    }
71
72    // Function to hide loading indicator
73    function hideLoadingIndicator() {
74        const loader = document.getElementById('wiki-ai-loader');
75        if (loader) {
76            loader.remove();
77            console.log('Loading indicator hidden');
78        }
79    }
80
81    // Function to display summary in a modal
82    function displaySummary(summaryData) {
83        console.log('Displaying summary:', summaryData);
84        
85        // Remove existing modal if any
86        const existingModal = document.getElementById('wiki-ai-summary-modal');
87        if (existingModal) {
88            existingModal.remove();
89        }
90
91        const modal = document.createElement('div');
92        modal.id = 'wiki-ai-summary-modal';
93        modal.innerHTML = `
94            <div class="modal-content">
95                <div class="modal-header">
96                    <h2>📚 AI Summary</h2>
97                    <button class="close-btn" id="close-summary-modal">&times;</button>
98                </div>
99                <div class="modal-body">
100                    <div class="summary-section">
101                        <h3>🎯 Quick Summary</h3>
102                        <p>${summaryData.summary}</p>
103                    </div>
104                    
105                    <div class="summary-section">
106                        <h3>🔑 Key Points</h3>
107                        <ul>
108                            ${summaryData.keyPoints.map(point => `<li>${point}</li>`).join('')}
109                        </ul>
110                    </div>
111                    
112                    <div class="summary-section">
113                        <h3>💡 Main Takeaway</h3>
114                        <p>${summaryData.mainTakeaway}</p>
115                    </div>
116                    
117                    ${summaryData.historicalContext ? `
118                    <div class="summary-section">
119                        <h3>📅 Historical Context</h3>
120                        <p>${summaryData.historicalContext}</p>
121                    </div>
122                    ` : ''}
123                </div>
124            </div>
125        `;
126
127        document.body.appendChild(modal);
128
129        // Add event listeners
130        document.getElementById('close-summary-modal').addEventListener('click', () => {
131            modal.remove();
132        });
133
134        modal.addEventListener('click', (e) => {
135            if (e.target === modal) {
136                modal.remove();
137            }
138        });
139
140        // Close on Escape key
141        const escapeHandler = (e) => {
142            if (e.key === 'Escape') {
143                modal.remove();
144                document.removeEventListener('keydown', escapeHandler);
145            }
146        };
147        document.addEventListener('keydown', escapeHandler);
148    }
149
150    // Main function to generate AI summary
151    async function generateAISummary() {
152        console.log('Starting AI summary generation');
153        
154        const articleTitle = getArticleTitle();
155        const articleContent = getArticleContent();
156
157        if (!articleContent) {
158            alert('Could not extract article content. Please make sure you are on a Wikipedia article page.');
159            return;
160        }
161
162        showLoadingIndicator();
163
164        try {
165            // Use RM.aiCall with structured output for better results
166            const summaryData = await RM.aiCall(
167                `You are a helpful AI assistant that creates concise, informative summaries of Wikipedia articles.
168                
169Article Title: ${articleTitle}
170
171Article Content:
172${articleContent}
173
174Please analyze this Wikipedia article and provide a comprehensive summary.`,
175                {
176                    type: "json_schema",
177                    json_schema: {
178                        name: "wikipedia_summary",
179                        schema: {
180                            type: "object",
181                            properties: {
182                                summary: {
183                                    type: "string",
184                                    description: "A concise 2-3 sentence summary of the entire article"
185                                },
186                                keyPoints: {
187                                    type: "array",
188                                    description: "3-5 key points or facts from the article",
189                                    items: { type: "string" },
190                                    minItems: 3,
191                                    maxItems: 5
192                                },
193                                mainTakeaway: {
194                                    type: "string",
195                                    description: "The single most important thing to understand from this article"
196                                },
197                                historicalContext: {
198                                    type: "string",
199                                    description: "Brief historical context if applicable, otherwise empty string"
200                                }
201                            },
202                            required: ["summary", "keyPoints", "mainTakeaway"]
203                        }
204                    }
205                }
206            );
207
208            console.log('AI summary generated successfully:', summaryData);
209            hideLoadingIndicator();
210            displaySummary(summaryData);
211
212            // Cache the summary
213            const cacheKey = 'wiki_summary_' + window.location.pathname;
214            await GM.setValue(cacheKey, JSON.stringify({
215                data: summaryData,
216                timestamp: Date.now()
217            }));
218            console.log('Summary cached');
219
220        } catch (error) {
221            console.error('Error generating AI summary:', error);
222            hideLoadingIndicator();
223            alert('Failed to generate summary. Please try again. Error: ' + error.message);
224        }
225    }
226
227    // Function to check if we have a cached summary
228    async function getCachedSummary() {
229        const cacheKey = 'wiki_summary_' + window.location.pathname;
230        const cached = await GM.getValue(cacheKey);
231        
232        if (cached) {
233            try {
234                const parsedCache = JSON.parse(cached);
235                // Cache expires after 24 hours
236                const cacheAge = Date.now() - parsedCache.timestamp;
237                if (cacheAge < 24 * 60 * 60 * 1000) {
238                    console.log('Using cached summary');
239                    return parsedCache.data;
240                }
241            } catch (e) {
242                console.error('Error parsing cached summary:', e);
243            }
244        }
245        return null;
246    }
247
248    // Function to create and add the summarize button
249    function createSummarizeButton() {
250        console.log('Creating summarize button');
251
252        // Check if button already exists
253        if (document.getElementById('wiki-ai-summarize-btn')) {
254            console.log('Button already exists');
255            return;
256        }
257
258        // Find the views menu where we'll add our button
259        const viewsMenu = document.querySelector('#p-views .vector-menu-content-list');
260        
261        if (!viewsMenu) {
262            console.error('Could not find views menu');
263            return;
264        }
265
266        // Create the button as a list item matching Wikipedia's style
267        const buttonLi = document.createElement('li');
268        buttonLi.id = 'ca-ai-summarize';
269        buttonLi.className = 'vector-tab-noicon mw-list-item';
270        
271        const buttonLink = document.createElement('a');
272        buttonLink.href = '#';
273        buttonLink.id = 'wiki-ai-summarize-btn';
274        buttonLink.innerHTML = '<span>✨ AI Summary</span>';
275        buttonLink.title = 'Generate AI-powered summary of this article';
276        
277        buttonLink.addEventListener('click', async (e) => {
278            e.preventDefault();
279            console.log('Summarize button clicked');
280            
281            // Check for cached summary first
282            const cachedSummary = await getCachedSummary();
283            if (cachedSummary) {
284                displaySummary(cachedSummary);
285            } else {
286                generateAISummary();
287            }
288        });
289
290        buttonLi.appendChild(buttonLink);
291        viewsMenu.appendChild(buttonLi);
292        
293        console.log('Summarize button added successfully');
294    }
295
296    // Add CSS styles
297    function addStyles() {
298        const style = document.createElement('style');
299        style.textContent = `
300            /* Loading indicator styles */
301            #wiki-ai-loader {
302                position: fixed;
303                top: 20px;
304                right: 20px;
305                background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
306                color: white;
307                padding: 16px 24px;
308                border-radius: 12px;
309                box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
310                z-index: 10000;
311                font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
312                font-size: 14px;
313                font-weight: 500;
314                animation: slideIn 0.3s ease-out;
315            }
316
317            @keyframes slideIn {
318                from {
319                    transform: translateX(400px);
320                    opacity: 0;
321                }
322                to {
323                    transform: translateX(0);
324                    opacity: 1;
325                }
326            }
327
328            #wiki-ai-loader .spinner {
329                width: 20px;
330                height: 20px;
331                border: 3px solid rgba(255, 255, 255, 0.3);
332                border-top-color: white;
333                border-radius: 50%;
334                animation: spin 0.8s linear infinite;
335            }
336
337            @keyframes spin {
338                to { transform: rotate(360deg); }
339            }
340
341            /* Modal styles */
342            #wiki-ai-summary-modal {
343                position: fixed;
344                top: 0;
345                left: 0;
346                width: 100%;
347                height: 100%;
348                background: rgba(0, 0, 0, 0.7);
349                display: flex;
350                justify-content: center;
351                align-items: center;
352                z-index: 9999;
353                animation: fadeIn 0.3s ease-out;
354                padding: 20px;
355                box-sizing: border-box;
356            }
357
358            @keyframes fadeIn {
359                from { opacity: 0; }
360                to { opacity: 1; }
361            }
362
363            #wiki-ai-summary-modal .modal-content {
364                background: white;
365                border-radius: 16px;
366                max-width: 700px;
367                width: 100%;
368                max-height: 85vh;
369                overflow-y: auto;
370                box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
371                animation: slideUp 0.3s ease-out;
372            }
373
374            @keyframes slideUp {
375                from {
376                    transform: translateY(50px);
377                    opacity: 0;
378                }
379                to {
380                    transform: translateY(0);
381                    opacity: 1;
382                }
383            }
384
385            #wiki-ai-summary-modal .modal-header {
386                display: flex;
387                justify-content: space-between;
388                align-items: center;
389                padding: 24px 28px;
390                border-bottom: 2px solid #f0f0f0;
391                background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
392                color: white;
393                border-radius: 16px 16px 0 0;
394            }
395
396            #wiki-ai-summary-modal .modal-header h2 {
397                margin: 0;
398                font-size: 24px;
399                font-weight: 700;
400                font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
401            }
402
403            #wiki-ai-summary-modal .close-btn {
404                background: rgba(255, 255, 255, 0.2);
405                border: none;
406                color: white;
407                font-size: 32px;
408                cursor: pointer;
409                width: 40px;
410                height: 40px;
411                border-radius: 50%;
412                display: flex;
413                align-items: center;
414                justify-content: center;
415                transition: all 0.2s;
416                line-height: 1;
417                padding: 0;
418            }
419
420            #wiki-ai-summary-modal .close-btn:hover {
421                background: rgba(255, 255, 255, 0.3);
422                transform: rotate(90deg);
423            }
424
425            #wiki-ai-summary-modal .modal-body {
426                padding: 28px;
427                font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
428                color: #333;
429                line-height: 1.7;
430            }
431
432            #wiki-ai-summary-modal .summary-section {
433                margin-bottom: 28px;
434                padding: 20px;
435                background: #f8f9fa;
436                border-radius: 12px;
437                border-left: 4px solid #667eea;
438            }
439
440            #wiki-ai-summary-modal .summary-section:last-child {
441                margin-bottom: 0;
442            }
443
444            #wiki-ai-summary-modal .summary-section h3 {
445                margin: 0 0 12px 0;
446                font-size: 18px;
447                font-weight: 600;
448                color: #667eea;
449            }
450
451            #wiki-ai-summary-modal .summary-section p {
452                margin: 0;
453                font-size: 15px;
454                color: #2c3e50;
455            }
456
457            #wiki-ai-summary-modal .summary-section ul {
458                margin: 0;
459                padding-left: 24px;
460            }
461
462            #wiki-ai-summary-modal .summary-section li {
463                margin-bottom: 10px;
464                font-size: 15px;
465                color: #2c3e50;
466            }
467
468            #wiki-ai-summary-modal .summary-section li:last-child {
469                margin-bottom: 0;
470            }
471
472            /* Button hover effect */
473            #wiki-ai-summarize-btn:hover {
474                background: rgba(102, 126, 234, 0.1);
475            }
476
477            /* Scrollbar styling for modal */
478            #wiki-ai-summary-modal .modal-content::-webkit-scrollbar {
479                width: 8px;
480            }
481
482            #wiki-ai-summary-modal .modal-content::-webkit-scrollbar-track {
483                background: #f1f1f1;
484                border-radius: 0 16px 16px 0;
485            }
486
487            #wiki-ai-summary-modal .modal-content::-webkit-scrollbar-thumb {
488                background: #667eea;
489                border-radius: 4px;
490            }
491
492            #wiki-ai-summary-modal .modal-content::-webkit-scrollbar-thumb:hover {
493                background: #764ba2;
494            }
495        `;
496        document.head.appendChild(style);
497        console.log('Styles added');
498    }
499
500    // Initialize the extension
501    function init() {
502        console.log('Initializing Wikipedia AI Summarizer');
503        
504        // Check if we're on an article page (not main page, special pages, etc.)
505        const isArticlePage = document.querySelector('h1#firstHeading') && 
506                             !window.location.pathname.includes('Special:') &&
507                             window.location.pathname !== '/wiki/Main_Page';
508
509        if (!isArticlePage) {
510            console.log('Not on an article page, skipping initialization');
511            return;
512        }
513
514        addStyles();
515        createSummarizeButton();
516        
517        console.log('Wikipedia AI Summarizer initialized successfully');
518    }
519
520    // Wait for the page to be fully loaded
521    if (document.readyState === 'loading') {
522        document.addEventListener('DOMContentLoaded', init);
523    } else {
524        init();
525    }
526
527    // Handle navigation changes (for single-page navigation)
528    const debouncedInit = debounce(init, 500);
529    const observer = new MutationObserver(debouncedInit);
530    observer.observe(document.body, { childList: true, subtree: true });
531
532})();
Wikipedia AI Summarizer | Robomonkey