Grammar Checker Assistant

AI-powered grammar and spelling checker for text inputs and textareas

Size

12.1 KB

Version

1.1.3

Created

Oct 25, 2025

Updated

about 1 month ago

1// ==UserScript==
2// @name		Grammar Checker Assistant
3// @description		AI-powered grammar and spelling checker for text inputs and textareas
4// @version		1.1.3
5// @match		https://*.robomonkey.io/*
6// @icon		https://robomonkey.io/icon.png?adc3438f5fbb5315
7// ==/UserScript==
8(function() {
9    'use strict';
10
11    // Debounce function to avoid excessive API calls
12    function debounce(func, wait) {
13        let timeout;
14        return function executedFunction(...args) {
15            const later = () => {
16                clearTimeout(timeout);
17                func(...args);
18            };
19            clearTimeout(timeout);
20            timeout = setTimeout(later, wait);
21        };
22    }
23
24    // Create grammar check UI
25    function createGrammarUI(element) {
26        // Check if UI already exists for this element
27        if (element.dataset.grammarCheckerEnabled) {
28            return;
29        }
30        element.dataset.grammarCheckerEnabled = 'true';
31
32        // Create container for grammar suggestions
33        const container = document.createElement('div');
34        container.className = 'grammar-checker-container';
35        container.style.cssText = `
36            position: absolute;
37            background: white;
38            border: 1px solid #e0e0e0;
39            border-radius: 8px;
40            padding: 12px;
41            box-shadow: 0 4px 12px rgba(0,0,0,0.15);
42            z-index: 10000;
43            max-width: 350px;
44            display: none;
45            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
46            font-size: 14px;
47        `;
48
49        // Create loading indicator
50        const loadingIndicator = document.createElement('div');
51        loadingIndicator.className = 'grammar-loading';
52        loadingIndicator.style.cssText = `
53            position: absolute;
54            top: -30px;
55            right: 0;
56            background: #4CAF50;
57            color: white;
58            padding: 6px 12px;
59            border-radius: 4px;
60            font-size: 12px;
61            display: none;
62            z-index: 10001;
63        `;
64        loadingIndicator.textContent = '✓ Checking grammar...';
65
66        // Position container relative to element
67        element.style.position = 'relative';
68        element.parentElement.style.position = 'relative';
69        element.parentElement.appendChild(container);
70        element.parentElement.appendChild(loadingIndicator);
71
72        // Check grammar function
73        const checkGrammar = debounce(async (text) => {
74            if (!text || text.trim().length < 3) {
75                container.style.display = 'none';
76                return;
77            }
78
79            loadingIndicator.style.display = 'block';
80
81            try {
82                const result = await RM.aiCall(
83                    `Check the following text for grammar, spelling, and style issues. Provide specific corrections and improvements: "${text}"`,
84                    {
85                        type: 'json_schema',
86                        json_schema: {
87                            name: 'grammar_check',
88                            schema: {
89                                type: 'object',
90                                properties: {
91                                    hasIssues: { type: 'boolean' },
92                                    issues: {
93                                        type: 'array',
94                                        items: {
95                                            type: 'object',
96                                            properties: {
97                                                type: { type: 'string', enum: ['grammar', 'spelling', 'style', 'punctuation'] },
98                                                original: { type: 'string' },
99                                                suggestion: { type: 'string' },
100                                                explanation: { type: 'string' }
101                                            },
102                                            required: ['type', 'original', 'suggestion', 'explanation']
103                                        }
104                                    },
105                                    correctedText: { type: 'string' }
106                                },
107                                required: ['hasIssues', 'issues']
108                            }
109                        }
110                    }
111                );
112
113                loadingIndicator.style.display = 'none';
114
115                if (result.hasIssues && result.issues.length > 0) {
116                    displaySuggestions(result, element, container);
117                } else {
118                    container.innerHTML = '<div style="color: #4CAF50; font-weight: 500;">✓ No issues found!</div>';
119                    container.style.display = 'block';
120                    setTimeout(() => {
121                        container.style.display = 'none';
122                    }, 2000);
123                }
124
125            } catch (error) {
126                console.error('Grammar check failed:', error);
127                loadingIndicator.style.display = 'none';
128                container.innerHTML = '<div style="color: #f44336;">Error checking grammar. Please try again.</div>';
129                container.style.display = 'block';
130            }
131        }, 1500);
132
133        // Display suggestions
134        function displaySuggestions(result, element, container) {
135            container.innerHTML = '';
136            
137            const title = document.createElement('div');
138            title.style.cssText = 'font-weight: 600; margin-bottom: 10px; color: #333; font-size: 15px;';
139            title.textContent = `Found ${result.issues.length} issue${result.issues.length > 1 ? 's' : ''}`;
140            container.appendChild(title);
141
142            result.issues.forEach((issue, index) => {
143                const issueDiv = document.createElement('div');
144                issueDiv.style.cssText = `
145                    margin-bottom: 12px;
146                    padding: 10px;
147                    background: #f5f5f5;
148                    border-radius: 6px;
149                    border-left: 3px solid ${getIssueColor(issue.type)};
150                `;
151
152                const typeLabel = document.createElement('div');
153                typeLabel.style.cssText = `
154                    font-size: 11px;
155                    text-transform: uppercase;
156                    color: ${getIssueColor(issue.type)};
157                    font-weight: 600;
158                    margin-bottom: 4px;
159                `;
160                typeLabel.textContent = issue.type;
161
162                const originalText = document.createElement('div');
163                originalText.style.cssText = 'text-decoration: line-through; color: #d32f2f; margin-bottom: 4px;';
164                originalText.textContent = issue.original;
165
166                const suggestionText = document.createElement('div');
167                suggestionText.style.cssText = 'color: #2e7d32; font-weight: 500; margin-bottom: 6px;';
168                suggestionText.textContent = '→ ' + issue.suggestion;
169
170                const explanation = document.createElement('div');
171                explanation.style.cssText = 'font-size: 12px; color: #666; margin-bottom: 8px;';
172                explanation.textContent = issue.explanation;
173
174                const applyBtn = document.createElement('button');
175                applyBtn.style.cssText = `
176                    background: #4CAF50;
177                    color: white;
178                    border: none;
179                    padding: 6px 12px;
180                    border-radius: 4px;
181                    cursor: pointer;
182                    font-size: 12px;
183                    font-weight: 500;
184                `;
185                applyBtn.textContent = 'Apply Fix';
186                applyBtn.onclick = () => {
187                    const currentText = element.value;
188                    element.value = currentText.replace(issue.original, issue.suggestion);
189                    element.dispatchEvent(new Event('input', { bubbles: true }));
190                    issueDiv.style.opacity = '0.5';
191                    applyBtn.disabled = true;
192                    applyBtn.textContent = 'Applied ✓';
193                };
194
195                issueDiv.appendChild(typeLabel);
196                issueDiv.appendChild(originalText);
197                issueDiv.appendChild(suggestionText);
198                issueDiv.appendChild(explanation);
199                issueDiv.appendChild(applyBtn);
200                container.appendChild(issueDiv);
201            });
202
203            // Add "Fix All" button if there are multiple issues
204            if (result.issues.length > 1 && result.correctedText) {
205                const fixAllBtn = document.createElement('button');
206                fixAllBtn.style.cssText = `
207                    background: #2196F3;
208                    color: white;
209                    border: none;
210                    padding: 8px 16px;
211                    border-radius: 4px;
212                    cursor: pointer;
213                    font-size: 13px;
214                    font-weight: 500;
215                    width: 100%;
216                    margin-top: 8px;
217                `;
218                fixAllBtn.textContent = 'Fix All Issues';
219                fixAllBtn.onclick = () => {
220                    element.value = result.correctedText;
221                    element.dispatchEvent(new Event('input', { bubbles: true }));
222                    container.innerHTML = '<div style="color: #4CAF50; font-weight: 500; text-align: center;">✓ All issues fixed!</div>';
223                    setTimeout(() => {
224                        container.style.display = 'none';
225                    }, 2000);
226                };
227                container.appendChild(fixAllBtn);
228            }
229
230            container.style.display = 'block';
231            positionContainer(element, container);
232        }
233
234        function getIssueColor(type) {
235            const colors = {
236                grammar: '#f44336',
237                spelling: '#ff9800',
238                style: '#2196F3',
239                punctuation: '#9c27b0'
240            };
241            return colors[type] || '#757575';
242        }
243
244        function positionContainer(element, container) {
245            const rect = element.getBoundingClientRect();
246            container.style.top = (rect.height + 5) + 'px';
247            container.style.left = '0px';
248        }
249
250        // Listen to input events
251        element.addEventListener('input', (e) => {
252            checkGrammar(e.target.value);
253        });
254
255        // Close container when clicking outside
256        document.addEventListener('click', (e) => {
257            if (!container.contains(e.target) && e.target !== element) {
258                container.style.display = 'none';
259            }
260        });
261
262        console.log('Grammar checker enabled for element:', element);
263    }
264
265    // Initialize grammar checker on text inputs and textareas
266    function init() {
267        console.log('Grammar Checker Assistant initialized');
268
269        // Find all text inputs and textareas
270        const textElements = document.querySelectorAll('textarea, input[type="text"], [contenteditable="true"]');
271        
272        textElements.forEach(element => {
273            createGrammarUI(element);
274        });
275
276        // Watch for dynamically added elements
277        const observer = new MutationObserver((mutations) => {
278            mutations.forEach((mutation) => {
279                mutation.addedNodes.forEach((node) => {
280                    if (node.nodeType === 1) {
281                        if (node.matches('textarea, input[type="text"], [contenteditable="true"]')) {
282                            createGrammarUI(node);
283                        }
284                        const textElements = node.querySelectorAll('textarea, input[type="text"], [contenteditable="true"]');
285                        textElements.forEach(element => {
286                            createGrammarUI(element);
287                        });
288                    }
289                });
290            });
291        });
292
293        observer.observe(document.body, {
294            childList: true,
295            subtree: true
296        });
297    }
298
299    // Start when page is ready
300    if (document.readyState === 'loading') {
301        document.addEventListener('DOMContentLoaded', init);
302    } else {
303        init();
304    }
305
306})();
Grammar Checker Assistant | Robomonkey