Task Summarizer for Google Sheets

AI-powered task summarizer that analyzes and summarizes tasks in Google Sheets

Size

14.3 KB

Version

1.0.1

Created

Oct 25, 2025

Updated

21 days ago

1// ==UserScript==
2// @name		Task Summarizer for Google Sheets
3// @description		AI-powered task summarizer that analyzes and summarizes tasks in Google Sheets
4// @version		1.0.1
5// @match		https://*.docs.google.com/*
6// @icon		https://ssl.gstatic.com/docs/spreadsheets/spreadsheets_2023q4.ico
7// ==/UserScript==
8(function() {
9    'use strict';
10
11    console.log('Task Summarizer for Google Sheets initialized');
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    // Extract data from Google Sheets
27    function extractSheetData() {
28        console.log('Extracting sheet data...');
29        
30        // Try multiple methods to get cell data
31        const data = [];
32        
33        // Method 1: Try to get selected cells
34        const selectedCells = document.querySelectorAll('.s-selected');
35        if (selectedCells.length > 0) {
36            selectedCells.forEach(cell => {
37                const text = cell.textContent.trim();
38                if (text) data.push(text);
39            });
40        }
41        
42        // Method 2: If no selection, get all visible cells with content
43        if (data.length === 0) {
44            const allCells = document.querySelectorAll('[class*="s"]');
45            allCells.forEach(cell => {
46                const text = cell.textContent.trim();
47                if (text && text.length > 0 && text !== 'Add' && !text.includes('more rows')) {
48                    data.push(text);
49                }
50            });
51        }
52        
53        console.log(`Extracted ${data.length} cells with data`);
54        return data;
55    }
56
57    // Create the summarizer button
58    function createSummarizerButton() {
59        console.log('Creating summarizer button...');
60        
61        // Check if button already exists
62        if (document.getElementById('task-summarizer-btn')) {
63            console.log('Button already exists');
64            return;
65        }
66        
67        // Find the toolbar area
68        const toolbar = document.querySelector('#docs-titlebar-buttons');
69        if (!toolbar) {
70            console.log('Toolbar not found, retrying...');
71            setTimeout(createSummarizerButton, 1000);
72            return;
73        }
74        
75        // Create button container
76        const buttonContainer = document.createElement('div');
77        buttonContainer.className = 'goog-inline-block';
78        buttonContainer.style.marginRight = '8px';
79        
80        // Create the button
81        const button = document.createElement('div');
82        button.id = 'task-summarizer-btn';
83        button.className = 'goog-inline-block jfk-button jfk-button-standard docs-appbar-circle-button docs-titlebar-button';
84        button.setAttribute('role', 'button');
85        button.setAttribute('aria-label', 'Summarize Tasks');
86        button.setAttribute('data-tooltip', 'Summarize Tasks with AI');
87        button.style.cursor = 'pointer';
88        
89        // Add icon
90        button.innerHTML = `
91            <div class="docs-icon goog-inline-block">
92                <div class="docs-icon-img-container" style="display: flex; align-items: center; justify-content: center; width: 24px; height: 24px;">
93                    <span style="font-size: 18px;">📝</span>
94                </div>
95            </div>
96        `;
97        
98        // Add click handler
99        button.addEventListener('click', handleSummarizeClick);
100        
101        buttonContainer.appendChild(button);
102        toolbar.insertBefore(buttonContainer, toolbar.firstChild);
103        
104        console.log('Summarizer button created successfully');
105    }
106
107    // Handle summarize button click
108    async function handleSummarizeClick() {
109        console.log('Summarize button clicked');
110        
111        try {
112            // Show loading state
113            showNotification('Analyzing tasks...', 'info');
114            
115            // Extract data from sheet
116            const sheetData = extractSheetData();
117            
118            if (sheetData.length === 0) {
119                showNotification('No data found in the sheet. Please add some tasks first.', 'warning');
120                return;
121            }
122            
123            console.log('Sheet data:', sheetData);
124            
125            // Prepare prompt for AI
126            const prompt = `Analyze the following task data from a Google Sheet and provide a comprehensive summary:
127
128Tasks/Data:
129${sheetData.join('\n')}
130
131Please provide:
1321. A brief overview of the tasks
1332. Key priorities or important items
1343. Any patterns or categories you notice
1354. Suggested next actions or recommendations
136
137Format the response in a clear, organized way.`;
138            
139            // Call AI to summarize
140            console.log('Calling AI for summary...');
141            const summary = await RM.aiCall(prompt, {
142                type: "json_schema",
143                json_schema: {
144                    name: "task_summary",
145                    schema: {
146                        type: "object",
147                        properties: {
148                            overview: { type: "string" },
149                            keyPriorities: { 
150                                type: "array", 
151                                items: { type: "string" } 
152                            },
153                            patterns: { type: "string" },
154                            recommendations: { 
155                                type: "array", 
156                                items: { type: "string" } 
157                            },
158                            totalTasks: { type: "number" }
159                        },
160                        required: ["overview", "keyPriorities", "recommendations"]
161                    }
162                }
163            });
164            
165            console.log('AI summary received:', summary);
166            
167            // Display the summary
168            displaySummary(summary);
169            
170        } catch (error) {
171            console.error('Error summarizing tasks:', error);
172            showNotification('Failed to summarize tasks. Please try again.', 'error');
173        }
174    }
175
176    // Display summary in a modal
177    function displaySummary(summary) {
178        console.log('Displaying summary modal');
179        
180        // Remove existing modal if any
181        const existingModal = document.getElementById('task-summary-modal');
182        if (existingModal) {
183            existingModal.remove();
184        }
185        
186        // Create modal overlay
187        const overlay = document.createElement('div');
188        overlay.id = 'task-summary-modal';
189        overlay.style.cssText = `
190            position: fixed;
191            top: 0;
192            left: 0;
193            width: 100%;
194            height: 100%;
195            background: rgba(0, 0, 0, 0.5);
196            display: flex;
197            align-items: center;
198            justify-content: center;
199            z-index: 10000;
200        `;
201        
202        // Create modal content
203        const modal = document.createElement('div');
204        modal.style.cssText = `
205            background: white;
206            border-radius: 8px;
207            padding: 24px;
208            max-width: 600px;
209            max-height: 80vh;
210            overflow-y: auto;
211            box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
212        `;
213        
214        // Build summary HTML
215        let summaryHTML = `
216            <div style="margin-bottom: 20px;">
217                <h2 style="margin: 0 0 16px 0; color: #1a73e8; font-size: 24px; font-weight: 500;">
218                    📊 Task Summary
219                </h2>
220            </div>
221            
222            <div style="margin-bottom: 20px;">
223                <h3 style="color: #333; font-size: 16px; font-weight: 500; margin-bottom: 8px;">
224                    Overview
225                </h3>
226                <p style="color: #555; line-height: 1.6; margin: 0;">
227                    ${summary.overview}
228                </p>
229            </div>
230        `;
231        
232        if (summary.keyPriorities && summary.keyPriorities.length > 0) {
233            summaryHTML += `
234                <div style="margin-bottom: 20px;">
235                    <h3 style="color: #333; font-size: 16px; font-weight: 500; margin-bottom: 8px;">
236                        🎯 Key Priorities
237                    </h3>
238                    <ul style="color: #555; line-height: 1.8; margin: 0; padding-left: 20px;">
239                        ${summary.keyPriorities.map(priority => `<li>${priority}</li>`).join('')}
240                    </ul>
241                </div>
242            `;
243        }
244        
245        if (summary.patterns) {
246            summaryHTML += `
247                <div style="margin-bottom: 20px;">
248                    <h3 style="color: #333; font-size: 16px; font-weight: 500; margin-bottom: 8px;">
249                        🔍 Patterns & Insights
250                    </h3>
251                    <p style="color: #555; line-height: 1.6; margin: 0;">
252                        ${summary.patterns}
253                    </p>
254                </div>
255            `;
256        }
257        
258        if (summary.recommendations && summary.recommendations.length > 0) {
259            summaryHTML += `
260                <div style="margin-bottom: 20px;">
261                    <h3 style="color: #333; font-size: 16px; font-weight: 500; margin-bottom: 8px;">
262                        💡 Recommendations
263                    </h3>
264                    <ul style="color: #555; line-height: 1.8; margin: 0; padding-left: 20px;">
265                        ${summary.recommendations.map(rec => `<li>${rec}</li>`).join('')}
266                    </ul>
267                </div>
268            `;
269        }
270        
271        if (summary.totalTasks) {
272            summaryHTML += `
273                <div style="margin-bottom: 20px; padding: 12px; background: #f1f3f4; border-radius: 4px;">
274                    <strong style="color: #333;">Total Items Analyzed:</strong> 
275                    <span style="color: #1a73e8; font-weight: 500;">${summary.totalTasks}</span>
276                </div>
277            `;
278        }
279        
280        // Add close button
281        summaryHTML += `
282            <div style="text-align: right; margin-top: 20px;">
283                <button id="close-summary-btn" style="
284                    background: #1a73e8;
285                    color: white;
286                    border: none;
287                    padding: 10px 24px;
288                    border-radius: 4px;
289                    cursor: pointer;
290                    font-size: 14px;
291                    font-weight: 500;
292                ">
293                    Close
294                </button>
295            </div>
296        `;
297        
298        modal.innerHTML = summaryHTML;
299        overlay.appendChild(modal);
300        document.body.appendChild(overlay);
301        
302        // Add close handlers
303        document.getElementById('close-summary-btn').addEventListener('click', () => {
304            overlay.remove();
305        });
306        
307        overlay.addEventListener('click', (e) => {
308            if (e.target === overlay) {
309                overlay.remove();
310            }
311        });
312    }
313
314    // Show notification
315    function showNotification(message, type = 'info') {
316        console.log(`Notification (${type}):`, message);
317        
318        // Remove existing notification
319        const existing = document.getElementById('task-summarizer-notification');
320        if (existing) {
321            existing.remove();
322        }
323        
324        const notification = document.createElement('div');
325        notification.id = 'task-summarizer-notification';
326        
327        const bgColor = type === 'error' ? '#d93025' : 
328                       type === 'warning' ? '#f9ab00' : 
329                       type === 'success' ? '#1e8e3e' : '#1a73e8';
330        
331        notification.style.cssText = `
332            position: fixed;
333            top: 80px;
334            right: 20px;
335            background: ${bgColor};
336            color: white;
337            padding: 16px 24px;
338            border-radius: 4px;
339            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
340            z-index: 9999;
341            font-size: 14px;
342            max-width: 400px;
343            animation: slideIn 0.3s ease-out;
344        `;
345        
346        notification.textContent = message;
347        document.body.appendChild(notification);
348        
349        // Auto-remove after 4 seconds
350        setTimeout(() => {
351            notification.style.animation = 'slideOut 0.3s ease-out';
352            setTimeout(() => notification.remove(), 300);
353        }, 4000);
354    }
355
356    // Add CSS animations
357    TM_addStyle(`
358        @keyframes slideIn {
359            from {
360                transform: translateX(400px);
361                opacity: 0;
362            }
363            to {
364                transform: translateX(0);
365                opacity: 1;
366            }
367        }
368        
369        @keyframes slideOut {
370            from {
371                transform: translateX(0);
372                opacity: 1;
373            }
374            to {
375                transform: translateX(400px);
376                opacity: 0;
377            }
378        }
379        
380        #task-summarizer-btn:hover {
381            background-color: rgba(0, 0, 0, 0.08) !important;
382        }
383        
384        #close-summary-btn:hover {
385            background: #1557b0 !important;
386        }
387    `);
388
389    // Initialize the extension
390    function init() {
391        console.log('Initializing Task Summarizer...');
392        
393        // Check if we're on a Google Sheets page
394        if (!window.location.href.includes('docs.google.com/spreadsheets')) {
395            console.log('Not on a Google Sheets page');
396            return;
397        }
398        
399        // Wait for the page to load
400        if (document.readyState === 'loading') {
401            document.addEventListener('DOMContentLoaded', init);
402            return;
403        }
404        
405        // Create the button with a delay to ensure toolbar is loaded
406        setTimeout(createSummarizerButton, 2000);
407        
408        // Also observe for dynamic toolbar changes
409        const observer = new MutationObserver(debounce(() => {
410            if (!document.getElementById('task-summarizer-btn')) {
411                createSummarizerButton();
412            }
413        }, 1000));
414        
415        observer.observe(document.body, {
416            childList: true,
417            subtree: true
418        });
419        
420        console.log('Task Summarizer initialized successfully');
421    }
422
423    // Start the extension
424    init();
425
426})();