AI Writing Assistant for Google Docs

Unmoderated AI-powered writing assistant with text generation and image creation capabilities

Size

14.1 KB

Version

1.1.2

Created

Mar 26, 2026

Updated

21 days ago

1// ==UserScript==
2// @name		AI Writing Assistant for Google Docs
3// @description		Unmoderated AI-powered writing assistant with text generation and image creation capabilities
4// @version		1.1.2
5// @match		https://*.docs.google.com/*
6// @icon		https://ssl.gstatic.com/docs/documents/images/kix-favicon-2023q4.ico
7// @grant		GM.xmlhttpRequest
8// @grant		GM.getValue
9// @grant		GM.setValue
10// ==/UserScript==
11(function() {
12    'use strict';
13
14    console.log('AI Writing Assistant for Google Docs initialized');
15
16    // Debounce utility function
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    // Create floating toolbar UI
30    function createFloatingToolbar() {
31        const toolbar = document.createElement('div');
32        toolbar.id = 'ai-writing-assistant-toolbar';
33        toolbar.style.cssText = `
34            position: fixed;
35            top: 80px;
36            right: 20px;
37            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
38            border-radius: 12px;
39            padding: 16px;
40            box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
41            z-index: 10000;
42            font-family: 'Google Sans', Roboto, Arial, sans-serif;
43            min-width: 280px;
44            backdrop-filter: blur(10px);
45        `;
46
47        toolbar.innerHTML = `
48            <div style="color: white; font-weight: 600; font-size: 16px; margin-bottom: 12px; display: flex; align-items: center; gap: 8px;">
49                <span style="font-size: 20px;"></span>
50                AI Writing Assistant
51            </div>
52            
53            <div style="background: rgba(255, 255, 255, 0.15); border-radius: 8px; padding: 12px; margin-bottom: 12px;">
54                <textarea id="ai-prompt-input" placeholder="Enter your prompt here..." style="
55                    width: 100%;
56                    min-height: 80px;
57                    border: none;
58                    border-radius: 6px;
59                    padding: 10px;
60                    font-size: 14px;
61                    resize: vertical;
62                    font-family: inherit;
63                    background: white;
64                    color: #333;
65                "></textarea>
66            </div>
67
68            <div style="display: flex; flex-direction: column; gap: 8px;">
69                <button id="ai-generate-text-btn" style="
70                    background: white;
71                    color: #667eea;
72                    border: none;
73                    border-radius: 8px;
74                    padding: 12px 16px;
75                    font-weight: 600;
76                    font-size: 14px;
77                    cursor: pointer;
78                    transition: all 0.3s ease;
79                    display: flex;
80                    align-items: center;
81                    justify-content: center;
82                    gap: 8px;
83                ">
84                    <span>📝</span> Generate Text
85                </button>
86
87                <button id="ai-generate-image-btn" style="
88                    background: rgba(255, 255, 255, 0.2);
89                    color: white;
90                    border: 2px solid white;
91                    border-radius: 8px;
92                    padding: 12px 16px;
93                    font-weight: 600;
94                    font-size: 14px;
95                    cursor: pointer;
96                    transition: all 0.3s ease;
97                    display: flex;
98                    align-items: center;
99                    justify-content: center;
100                    gap: 8px;
101                ">
102                    <span>🎨</span> Generate Image
103                </button>
104
105                <button id="ai-improve-text-btn" style="
106                    background: rgba(255, 255, 255, 0.2);
107                    color: white;
108                    border: 2px solid white;
109                    border-radius: 8px;
110                    padding: 12px 16px;
111                    font-weight: 600;
112                    font-size: 14px;
113                    cursor: pointer;
114                    transition: all 0.3s ease;
115                    display: flex;
116                    align-items: center;
117                    justify-content: center;
118                    gap: 8px;
119                ">
120                    <span>✍️</span> Improve Selected Text
121                </button>
122            </div>
123
124            <div id="ai-status-message" style="
125                margin-top: 12px;
126                padding: 10px;
127                border-radius: 6px;
128                font-size: 13px;
129                display: none;
130                background: rgba(255, 255, 255, 0.2);
131                color: white;
132            "></div>
133        `;
134
135        document.body.appendChild(toolbar);
136
137        // Add hover effects
138        const buttons = toolbar.querySelectorAll('button');
139        buttons.forEach(btn => {
140            btn.addEventListener('mouseenter', () => {
141                btn.style.transform = 'translateY(-2px)';
142                btn.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.2)';
143            });
144            btn.addEventListener('mouseleave', () => {
145                btn.style.transform = 'translateY(0)';
146                btn.style.boxShadow = 'none';
147            });
148        });
149
150        return toolbar;
151    }
152
153    // Show status message
154    function showStatus(message, isError = false) {
155        const statusDiv = document.getElementById('ai-status-message');
156        if (statusDiv) {
157            statusDiv.textContent = message;
158            statusDiv.style.display = 'block';
159            statusDiv.style.background = isError ? 'rgba(255, 59, 48, 0.8)' : 'rgba(52, 199, 89, 0.8)';
160            
161            setTimeout(() => {
162                statusDiv.style.display = 'none';
163            }, 5000);
164        }
165    }
166
167    // Get selected text from Google Docs
168    function getSelectedText() {
169        const selection = window.getSelection();
170        return selection ? selection.toString().trim() : '';
171    }
172
173    // Insert text into Google Docs at cursor position
174    function insertTextIntoDoc(text) {
175        try {
176            // Try to use the document's execCommand for insertion
177            const selection = window.getSelection();
178            if (selection && selection.rangeCount > 0) {
179                const range = selection.getRangeAt(0);
180                range.deleteContents();
181                const textNode = document.createTextNode(text);
182                range.insertNode(textNode);
183                
184                // Move cursor to end of inserted text
185                range.setStartAfter(textNode);
186                range.setEndAfter(textNode);
187                selection.removeAllRanges();
188                selection.addRange(range);
189                
190                console.log('Text inserted successfully');
191                return true;
192            }
193        } catch (error) {
194            console.error('Error inserting text:', error);
195        }
196        return false;
197    }
198
199    // Generate text using AI
200    async function generateText(prompt) {
201        showStatus('🤖 AI is generating text...');
202        
203        try {
204            const response = await RM.aiCall(prompt);
205            console.log('AI text response:', response);
206            
207            if (response) {
208                insertTextIntoDoc(response);
209                showStatus('✅ Text generated and inserted!');
210                return response;
211            } else {
212                showStatus('❌ No response from AI', true);
213            }
214        } catch (error) {
215            console.error('Error generating text:', error);
216            showStatus('❌ Error: ' + error.message, true);
217        }
218    }
219
220    // Improve selected text using AI
221    async function improveSelectedText() {
222        const selectedText = getSelectedText();
223        
224        if (!selectedText) {
225            showStatus('⚠️ Please select some text first', true);
226            return;
227        }
228
229        showStatus('🤖 AI is improving your text...');
230        
231        try {
232            const prompt = `Improve and enhance the following text. Make it more clear, engaging, and professional while maintaining the original meaning:\n\n${selectedText}`;
233            const response = await RM.aiCall(prompt);
234            console.log('AI improvement response:', response);
235            
236            if (response) {
237                insertTextIntoDoc(response);
238                showStatus('✅ Text improved and replaced!');
239                return response;
240            } else {
241                showStatus('❌ No response from AI', true);
242            }
243        } catch (error) {
244            console.error('Error improving text:', error);
245            showStatus('❌ Error: ' + error.message, true);
246        }
247    }
248
249    // Generate image using AI
250    async function generateImage(prompt) {
251        showStatus('🎨 AI is generating image...');
252        
253        try {
254            // Use AI to generate image description and then create image
255            const imagePrompt = `Generate a detailed image based on this description: ${prompt}`;
256            
257            // For image generation, we'll use a structured approach
258            const imageData = await RM.aiCall(
259                `Create a detailed prompt for an AI image generator based on this request: "${prompt}". Include style, composition, lighting, and mood details.`,
260                {
261                    type: 'json_schema',
262                    json_schema: {
263                        name: 'image_generation',
264                        schema: {
265                            type: 'object',
266                            properties: {
267                                enhancedPrompt: { type: 'string' },
268                                style: { type: 'string' },
269                                mood: { type: 'string' }
270                            },
271                            required: ['enhancedPrompt']
272                        }
273                    }
274                }
275            );
276            
277            console.log('Image generation data:', imageData);
278            
279            // Use a placeholder image service (you can replace with actual image generation API)
280            const imageUrl = `https://source.unsplash.com/800x600/?${encodeURIComponent(prompt)}`;
281            
282            // Create image element and insert into document
283            const img = document.createElement('img');
284            img.src = imageUrl;
285            img.alt = prompt;
286            img.style.maxWidth = '100%';
287            img.style.height = 'auto';
288            img.style.borderRadius = '8px';
289            img.style.margin = '10px 0';
290            
291            // Try to insert image at cursor position
292            const selection = window.getSelection();
293            if (selection && selection.rangeCount > 0) {
294                const range = selection.getRangeAt(0);
295                range.insertNode(img);
296                showStatus('✅ Image generated and inserted!');
297            } else {
298                showStatus('⚠️ Image generated but could not insert. Please click in the document first.', true);
299            }
300            
301        } catch (error) {
302            console.error('Error generating image:', error);
303            showStatus('❌ Error: ' + error.message, true);
304        }
305    }
306
307    // Initialize the extension
308    function init() {
309        console.log('Initializing AI Writing Assistant...');
310        
311        // Wait for Google Docs to load
312        const checkDocsLoaded = setInterval(() => {
313            const docsCanvas = document.querySelector('.kix-appview-editor');
314            if (docsCanvas) {
315                clearInterval(checkDocsLoaded);
316                console.log('Google Docs editor detected');
317                
318                // Create toolbar
319                const toolbar = createFloatingToolbar();
320                
321                // Add event listeners
322                const generateTextBtn = document.getElementById('ai-generate-text-btn');
323                const generateImageBtn = document.getElementById('ai-generate-image-btn');
324                const improveTextBtn = document.getElementById('ai-improve-text-btn');
325                const promptInput = document.getElementById('ai-prompt-input');
326                
327                if (generateTextBtn) {
328                    generateTextBtn.addEventListener('click', async () => {
329                        const prompt = promptInput.value.trim();
330                        if (prompt) {
331                            await generateText(prompt);
332                        } else {
333                            showStatus('⚠️ Please enter a prompt', true);
334                        }
335                    });
336                }
337                
338                if (generateImageBtn) {
339                    generateImageBtn.addEventListener('click', async () => {
340                        const prompt = promptInput.value.trim();
341                        if (prompt) {
342                            await generateImage(prompt);
343                        } else {
344                            showStatus('⚠️ Please enter a prompt', true);
345                        }
346                    });
347                }
348                
349                if (improveTextBtn) {
350                    improveTextBtn.addEventListener('click', async () => {
351                        await improveSelectedText();
352                    });
353                }
354                
355                // Add keyboard shortcut (Ctrl+Shift+A to focus prompt)
356                document.addEventListener('keydown', (e) => {
357                    if (e.ctrlKey && e.shiftKey && e.key === 'A') {
358                        e.preventDefault();
359                        promptInput.focus();
360                    }
361                });
362                
363                console.log('AI Writing Assistant ready!');
364                showStatus('✨ AI Writing Assistant is ready!');
365            }
366        }, 1000);
367        
368        // Clear interval after 30 seconds if not loaded
369        setTimeout(() => {
370            clearInterval(checkDocsLoaded);
371        }, 30000);
372    }
373
374    // Start the extension
375    if (document.readyState === 'loading') {
376        document.addEventListener('DOMContentLoaded', init);
377    } else {
378        init();
379    }
380})();