AI-powered assistant that automates messages, reviews conversations, and generates contextual responses for Google Messages
Size
23.6 KB
Version
1.0.1
Created
Nov 28, 2025
Updated
15 days ago
1// ==UserScript==
2// @name Smart AI Message Assistant
3// @description AI-powered assistant that automates messages, reviews conversations, and generates contextual responses for Google Messages
4// @version 1.0.1
5// @match https://*.messages.google.com/*
6// @icon https://ssl.gstatic.com/android-messages-web/images/2022.3/1x/messages_2022_96dp.png
7// @grant GM.getValue
8// @grant GM.setValue
9// @grant GM.xmlhttpRequest
10// ==/UserScript==
11(function() {
12 'use strict';
13
14 console.log('Smart AI Message Assistant initialized');
15
16 // Utility function to debounce
17 function debounce(func, wait) {
18 let timeout;
19 return function executedFunction(...args) {
20 const later = () => {
21 clearTimeout(timeout);
22 func(...args);
23 };
24 clearTimeout(timeout);
25 timeout = setTimeout(later, wait);
26 };
27 }
28
29 // Add custom styles for AI assistant UI
30 function addStyles() {
31 const styles = `
32 .ai-assistant-container {
33 position: fixed;
34 bottom: 20px;
35 right: 20px;
36 z-index: 10000;
37 font-family: 'Google Sans', Roboto, Arial, sans-serif;
38 }
39
40 .ai-assistant-button {
41 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
42 color: white;
43 border: none;
44 border-radius: 50%;
45 width: 60px;
46 height: 60px;
47 font-size: 24px;
48 cursor: pointer;
49 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
50 transition: all 0.3s ease;
51 display: flex;
52 align-items: center;
53 justify-content: center;
54 }
55
56 .ai-assistant-button:hover {
57 transform: scale(1.1);
58 box-shadow: 0 6px 16px rgba(0, 0, 0, 0.4);
59 }
60
61 .ai-assistant-panel {
62 position: fixed;
63 bottom: 90px;
64 right: 20px;
65 width: 380px;
66 max-height: 600px;
67 background: white;
68 border-radius: 16px;
69 box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
70 display: none;
71 flex-direction: column;
72 overflow: hidden;
73 z-index: 10001;
74 }
75
76 .ai-assistant-panel.active {
77 display: flex;
78 }
79
80 .ai-panel-header {
81 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
82 color: white;
83 padding: 16px 20px;
84 font-size: 18px;
85 font-weight: 500;
86 display: flex;
87 justify-content: space-between;
88 align-items: center;
89 }
90
91 .ai-panel-close {
92 background: none;
93 border: none;
94 color: white;
95 font-size: 24px;
96 cursor: pointer;
97 padding: 0;
98 width: 30px;
99 height: 30px;
100 display: flex;
101 align-items: center;
102 justify-content: center;
103 border-radius: 50%;
104 transition: background 0.2s;
105 }
106
107 .ai-panel-close:hover {
108 background: rgba(255, 255, 255, 0.2);
109 }
110
111 .ai-panel-content {
112 padding: 20px;
113 overflow-y: auto;
114 flex: 1;
115 }
116
117 .ai-feature-section {
118 margin-bottom: 20px;
119 }
120
121 .ai-feature-title {
122 font-size: 14px;
123 font-weight: 500;
124 color: #5f6368;
125 margin-bottom: 10px;
126 text-transform: uppercase;
127 letter-spacing: 0.5px;
128 }
129
130 .ai-button {
131 width: 100%;
132 padding: 12px 16px;
133 margin-bottom: 10px;
134 background: #f1f3f4;
135 border: none;
136 border-radius: 8px;
137 cursor: pointer;
138 font-size: 14px;
139 font-weight: 500;
140 color: #202124;
141 transition: all 0.2s;
142 text-align: left;
143 display: flex;
144 align-items: center;
145 gap: 10px;
146 }
147
148 .ai-button:hover {
149 background: #e8eaed;
150 transform: translateY(-1px);
151 }
152
153 .ai-button:active {
154 transform: translateY(0);
155 }
156
157 .ai-button-icon {
158 font-size: 18px;
159 }
160
161 .ai-suggestions-container {
162 margin-top: 15px;
163 padding: 15px;
164 background: #f8f9fa;
165 border-radius: 8px;
166 border-left: 4px solid #667eea;
167 }
168
169 .ai-suggestion-item {
170 padding: 10px;
171 margin-bottom: 8px;
172 background: white;
173 border-radius: 6px;
174 cursor: pointer;
175 transition: all 0.2s;
176 font-size: 14px;
177 line-height: 1.5;
178 }
179
180 .ai-suggestion-item:hover {
181 background: #e8f0fe;
182 transform: translateX(4px);
183 }
184
185 .ai-suggestion-item:last-child {
186 margin-bottom: 0;
187 }
188
189 .ai-loading {
190 display: flex;
191 align-items: center;
192 justify-content: center;
193 padding: 20px;
194 color: #5f6368;
195 font-size: 14px;
196 }
197
198 .ai-loading-spinner {
199 border: 3px solid #f3f3f3;
200 border-top: 3px solid #667eea;
201 border-radius: 50%;
202 width: 24px;
203 height: 24px;
204 animation: spin 1s linear infinite;
205 margin-right: 10px;
206 }
207
208 @keyframes spin {
209 0% { transform: rotate(0deg); }
210 100% { transform: rotate(360deg); }
211 }
212
213 .ai-context-display {
214 padding: 12px;
215 background: #e8f0fe;
216 border-radius: 8px;
217 font-size: 13px;
218 line-height: 1.6;
219 color: #202124;
220 margin-top: 10px;
221 max-height: 200px;
222 overflow-y: auto;
223 }
224
225 .ai-status-badge {
226 display: inline-block;
227 padding: 4px 8px;
228 border-radius: 12px;
229 font-size: 11px;
230 font-weight: 500;
231 margin-left: 8px;
232 }
233
234 .ai-status-active {
235 background: #e6f4ea;
236 color: #137333;
237 }
238
239 .ai-status-inactive {
240 background: #fce8e6;
241 color: #c5221f;
242 }
243
244 .compose-ai-toolbar {
245 display: flex;
246 gap: 8px;
247 padding: 8px;
248 background: #f8f9fa;
249 border-radius: 8px;
250 margin-bottom: 8px;
251 }
252
253 .compose-ai-button {
254 padding: 8px 12px;
255 background: white;
256 border: 1px solid #dadce0;
257 border-radius: 6px;
258 cursor: pointer;
259 font-size: 13px;
260 font-weight: 500;
261 color: #5f6368;
262 transition: all 0.2s;
263 display: flex;
264 align-items: center;
265 gap: 6px;
266 }
267
268 .compose-ai-button:hover {
269 background: #f1f3f4;
270 border-color: #667eea;
271 color: #667eea;
272 }
273 `;
274
275 const styleSheet = document.createElement('style');
276 styleSheet.textContent = styles;
277 document.head.appendChild(styleSheet);
278 }
279
280 // Extract conversation context
281 function extractConversationContext() {
282 const messages = [];
283 const messageElements = document.querySelectorAll('mws-message-wrapper[data-e2e-message-wrapper]');
284
285 messageElements.forEach(msgEl => {
286 const coreEl = msgEl.querySelector('[data-e2e-message-wrapper-core]');
287 if (!coreEl) return;
288
289 const isOutgoing = coreEl.getAttribute('data-e2e-message-outgoing') === 'true';
290 const textContent = msgEl.querySelector('.text-msg-content, [data-e2e-message-content]')?.textContent?.trim();
291
292 if (textContent) {
293 messages.push({
294 sender: isOutgoing ? 'You' : 'Contact',
295 text: textContent,
296 isOutgoing: isOutgoing
297 });
298 }
299 });
300
301 return messages;
302 }
303
304 // Get conversation summary
305 async function getConversationSummary() {
306 const messages = extractConversationContext();
307
308 if (messages.length === 0) {
309 return 'No messages found in this conversation.';
310 }
311
312 const conversationText = messages.map(m => `${m.sender}: ${m.text}`).join('\n');
313
314 try {
315 const summary = await RM.aiCall(
316 `Analyze this conversation and provide a brief summary with key points:\n\n${conversationText}`,
317 {
318 type: "json_schema",
319 json_schema: {
320 name: "conversation_summary",
321 schema: {
322 type: "object",
323 properties: {
324 summary: { type: "string" },
325 keyTopics: { type: "array", items: { type: "string" } },
326 sentiment: { type: "string", enum: ["positive", "neutral", "negative", "mixed"] },
327 suggestedActions: { type: "array", items: { type: "string" } }
328 },
329 required: ["summary", "keyTopics", "sentiment"]
330 }
331 }
332 }
333 );
334
335 return summary;
336 } catch (error) {
337 console.error('Error getting conversation summary:', error);
338 return { summary: 'Error analyzing conversation', keyTopics: [], sentiment: 'neutral' };
339 }
340 }
341
342 // Generate smart replies
343 async function generateSmartReplies() {
344 const messages = extractConversationContext();
345
346 if (messages.length === 0) {
347 return ['Hello!', 'How can I help?', 'Thanks for reaching out!'];
348 }
349
350 const lastMessages = messages.slice(-5);
351 const conversationText = lastMessages.map(m => `${m.sender}: ${m.text}`).join('\n');
352
353 try {
354 const replies = await RM.aiCall(
355 `Based on this conversation, generate 3 contextually appropriate reply suggestions:\n\n${conversationText}`,
356 {
357 type: "json_schema",
358 json_schema: {
359 name: "smart_replies",
360 schema: {
361 type: "object",
362 properties: {
363 replies: {
364 type: "array",
365 items: { type: "string" },
366 minItems: 3,
367 maxItems: 3
368 }
369 },
370 required: ["replies"]
371 }
372 }
373 }
374 );
375
376 return replies.replies;
377 } catch (error) {
378 console.error('Error generating smart replies:', error);
379 return ['Thanks for your message!', 'I appreciate that.', 'Sounds good!'];
380 }
381 }
382
383 // Generate custom response
384 async function generateCustomResponse(instruction) {
385 const messages = extractConversationContext();
386 const conversationText = messages.slice(-5).map(m => `${m.sender}: ${m.text}`).join('\n');
387
388 try {
389 const response = await RM.aiCall(
390 `Conversation context:\n${conversationText}\n\nUser instruction: ${instruction}\n\nGenerate an appropriate response:`
391 );
392
393 return response;
394 } catch (error) {
395 console.error('Error generating custom response:', error);
396 return 'Sorry, I could not generate a response.';
397 }
398 }
399
400 // Insert text into message input
401 function insertTextIntoCompose(text) {
402 // Try different selectors for the compose area
403 const composeSelectors = [
404 'textarea[aria-label*="message" i]',
405 'textarea[placeholder*="message" i]',
406 '[contenteditable="true"][aria-label*="message" i]',
407 'mws-message-compose textarea',
408 'mws-message-compose [contenteditable="true"]'
409 ];
410
411 let composeElement = null;
412 for (const selector of composeSelectors) {
413 composeElement = document.querySelector(selector);
414 if (composeElement) break;
415 }
416
417 if (!composeElement) {
418 console.error('Could not find compose element');
419 alert('Please click on the message input field first, then try again.');
420 return false;
421 }
422
423 // Set the value
424 if (composeElement.tagName === 'TEXTAREA' || composeElement.tagName === 'INPUT') {
425 composeElement.value = text;
426 composeElement.dispatchEvent(new Event('input', { bubbles: true }));
427 composeElement.dispatchEvent(new Event('change', { bubbles: true }));
428 } else if (composeElement.isContentEditable) {
429 composeElement.textContent = text;
430 composeElement.dispatchEvent(new Event('input', { bubbles: true }));
431 }
432
433 composeElement.focus();
434 return true;
435 }
436
437 // Create AI Assistant UI
438 function createAssistantUI() {
439 const container = document.createElement('div');
440 container.className = 'ai-assistant-container';
441
442 // Main button
443 const button = document.createElement('button');
444 button.className = 'ai-assistant-button';
445 button.innerHTML = '🤖';
446 button.title = 'AI Message Assistant';
447
448 // Panel
449 const panel = document.createElement('div');
450 panel.className = 'ai-assistant-panel';
451
452 panel.innerHTML = `
453 <div class="ai-panel-header">
454 <span>AI Assistant</span>
455 <button class="ai-panel-close">×</button>
456 </div>
457 <div class="ai-panel-content">
458 <div class="ai-feature-section">
459 <div class="ai-feature-title">📊 Conversation Analysis</div>
460 <button class="ai-button" data-action="analyze">
461 <span class="ai-button-icon">🔍</span>
462 <span>Analyze Conversation</span>
463 </button>
464 <button class="ai-button" data-action="summary">
465 <span class="ai-button-icon">📝</span>
466 <span>Get Summary</span>
467 </button>
468 </div>
469
470 <div class="ai-feature-section">
471 <div class="ai-feature-title">💬 Smart Replies</div>
472 <button class="ai-button" data-action="smart-replies">
473 <span class="ai-button-icon">✨</span>
474 <span>Generate Reply Suggestions</span>
475 </button>
476 <button class="ai-button" data-action="friendly">
477 <span class="ai-button-icon">😊</span>
478 <span>Friendly Response</span>
479 </button>
480 <button class="ai-button" data-action="professional">
481 <span class="ai-button-icon">💼</span>
482 <span>Professional Response</span>
483 </button>
484 <button class="ai-button" data-action="brief">
485 <span class="ai-button-icon">⚡</span>
486 <span>Brief Response</span>
487 </button>
488 </div>
489
490 <div class="ai-feature-section">
491 <div class="ai-feature-title">🎯 Quick Actions</div>
492 <button class="ai-button" data-action="thank">
493 <span class="ai-button-icon">🙏</span>
494 <span>Thank You Message</span>
495 </button>
496 <button class="ai-button" data-action="followup">
497 <span class="ai-button-icon">📞</span>
498 <span>Follow-up Message</span>
499 </button>
500 <button class="ai-button" data-action="decline">
501 <span class="ai-button-icon">🚫</span>
502 <span>Polite Decline</span>
503 </button>
504 </div>
505
506 <div id="ai-results-container"></div>
507 </div>
508 `;
509
510 container.appendChild(button);
511 container.appendChild(panel);
512 document.body.appendChild(container);
513
514 // Event listeners
515 button.addEventListener('click', () => {
516 panel.classList.toggle('active');
517 });
518
519 panel.querySelector('.ai-panel-close').addEventListener('click', () => {
520 panel.classList.remove('active');
521 });
522
523 // Action buttons
524 panel.querySelectorAll('.ai-button[data-action]').forEach(btn => {
525 btn.addEventListener('click', async () => {
526 const action = btn.getAttribute('data-action');
527 await handleAction(action);
528 });
529 });
530
531 console.log('AI Assistant UI created');
532 }
533
534 // Show loading state
535 function showLoading() {
536 const resultsContainer = document.getElementById('ai-results-container');
537 resultsContainer.innerHTML = `
538 <div class="ai-loading">
539 <div class="ai-loading-spinner"></div>
540 <span>AI is thinking...</span>
541 </div>
542 `;
543 }
544
545 // Display results
546 function displayResults(content) {
547 const resultsContainer = document.getElementById('ai-results-container');
548 resultsContainer.innerHTML = content;
549 }
550
551 // Handle actions
552 async function handleAction(action) {
553 showLoading();
554
555 try {
556 switch (action) {
557 case 'analyze':
558 await handleAnalyze();
559 break;
560 case 'summary':
561 await handleSummary();
562 break;
563 case 'smart-replies':
564 await handleSmartReplies();
565 break;
566 case 'friendly':
567 await handleCustomResponse('Generate a friendly, warm response');
568 break;
569 case 'professional':
570 await handleCustomResponse('Generate a professional, formal response');
571 break;
572 case 'brief':
573 await handleCustomResponse('Generate a brief, concise response');
574 break;
575 case 'thank':
576 await handleCustomResponse('Generate a thank you message');
577 break;
578 case 'followup':
579 await handleCustomResponse('Generate a follow-up message');
580 break;
581 case 'decline':
582 await handleCustomResponse('Generate a polite decline message');
583 break;
584 }
585 } catch (error) {
586 console.error('Error handling action:', error);
587 displayResults(`
588 <div class="ai-context-display" style="background: #fce8e6; color: #c5221f;">
589 Error: ${error.message || 'Something went wrong'}
590 </div>
591 `);
592 }
593 }
594
595 async function handleAnalyze() {
596 const messages = extractConversationContext();
597 const summary = await getConversationSummary();
598
599 displayResults(`
600 <div class="ai-suggestions-container">
601 <div class="ai-feature-title">Conversation Analysis</div>
602 <div class="ai-context-display">
603 <strong>Summary:</strong><br>${summary.summary}<br><br>
604 <strong>Key Topics:</strong><br>${summary.keyTopics?.join(', ') || 'None'}<br><br>
605 <strong>Sentiment:</strong> ${summary.sentiment}<br><br>
606 ${summary.suggestedActions ? `<strong>Suggested Actions:</strong><br>${summary.suggestedActions.join('<br>')}` : ''}
607 </div>
608 <div style="margin-top: 10px; font-size: 12px; color: #5f6368;">
609 Total messages: ${messages.length}
610 </div>
611 </div>
612 `);
613 }
614
615 async function handleSummary() {
616 const summary = await getConversationSummary();
617
618 displayResults(`
619 <div class="ai-suggestions-container">
620 <div class="ai-feature-title">Conversation Summary</div>
621 <div class="ai-context-display">
622 ${summary.summary}
623 </div>
624 </div>
625 `);
626 }
627
628 async function handleSmartReplies() {
629 const replies = await generateSmartReplies();
630
631 const repliesHTML = replies.map((reply, index) => `
632 <div class="ai-suggestion-item" data-reply="${reply.replace(/"/g, '"')}">
633 ${reply}
634 </div>
635 `).join('');
636
637 displayResults(`
638 <div class="ai-suggestions-container">
639 <div class="ai-feature-title">Smart Reply Suggestions</div>
640 ${repliesHTML}
641 </div>
642 `);
643
644 // Add click handlers to suggestions
645 document.querySelectorAll('.ai-suggestion-item').forEach(item => {
646 item.addEventListener('click', () => {
647 const reply = item.getAttribute('data-reply');
648 insertTextIntoCompose(reply);
649 });
650 });
651 }
652
653 async function handleCustomResponse(instruction) {
654 const response = await generateCustomResponse(instruction);
655
656 displayResults(`
657 <div class="ai-suggestions-container">
658 <div class="ai-feature-title">Generated Response</div>
659 <div class="ai-suggestion-item" data-reply="${response.replace(/"/g, '"')}">
660 ${response}
661 </div>
662 </div>
663 `);
664
665 // Add click handler
666 document.querySelector('.ai-suggestion-item').addEventListener('click', () => {
667 insertTextIntoCompose(response);
668 });
669 }
670
671 // Initialize
672 function init() {
673 console.log('Initializing Smart AI Message Assistant...');
674
675 // Wait for page to be ready
676 if (document.readyState === 'loading') {
677 document.addEventListener('DOMContentLoaded', init);
678 return;
679 }
680
681 // Add styles
682 addStyles();
683
684 // Wait a bit for the page to fully load
685 setTimeout(() => {
686 createAssistantUI();
687 console.log('Smart AI Message Assistant ready!');
688 }, 2000);
689 }
690
691 // Start the extension
692 init();
693})();