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})();