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