|
96 | 96 | sparklePath.removeAttribute("filter"); |
97 | 97 | }); |
98 | 98 |
|
| 99 | + // Add hover popup functionality |
| 100 | + let hoverTimeout; |
| 101 | + let popup = null; |
| 102 | + |
| 103 | + console.log('Threadly: Adding hover events to sparkle element'); |
| 104 | + console.log('Threadly: SVG element:', svg); |
| 105 | + console.log('Threadly: SVG position:', svg.getBoundingClientRect()); |
| 106 | + |
| 107 | + svg.addEventListener('mouseenter', () => { |
| 108 | + console.log('Threadly: Mouse entered sparkle - HOVER EVENT TRIGGERED!'); |
| 109 | + hoverTimeout = setTimeout(() => { |
| 110 | + console.log('Threadly: Creating popup after hover delay'); |
| 111 | + |
| 112 | + // Create a simple test popup first |
| 113 | + const testPopup = document.createElement('div'); |
| 114 | + testPopup.style.position = 'absolute'; |
| 115 | + testPopup.style.top = '-50px'; |
| 116 | + testPopup.style.left = '50%'; |
| 117 | + testPopup.style.transform = 'translateX(-50%)'; |
| 118 | + testPopup.style.background = 'red'; |
| 119 | + testPopup.style.color = 'white'; |
| 120 | + testPopup.style.padding = '10px'; |
| 121 | + testPopup.style.borderRadius = '5px'; |
| 122 | + testPopup.style.zIndex = '99999'; |
| 123 | + testPopup.textContent = 'TEST POPUP'; |
| 124 | + svg.style.position = 'relative'; |
| 125 | + svg.appendChild(testPopup); |
| 126 | + console.log('Threadly: Test popup created and appended'); |
| 127 | + console.log('Threadly: SVG children count:', svg.children.length); |
| 128 | + |
| 129 | + // Then try the real popup |
| 130 | + popup = createModeSelectionPopup(svg); |
| 131 | + if (popup) { |
| 132 | + console.log('Threadly: Popup created successfully'); |
| 133 | + } else { |
| 134 | + console.log('Threadly: Popup creation failed'); |
| 135 | + } |
| 136 | + }, 300); // Show popup after 300ms hover |
| 137 | + }); |
| 138 | + |
| 139 | + svg.addEventListener('mouseleave', () => { |
| 140 | + console.log('Threadly: Mouse left sparkle'); |
| 141 | + clearTimeout(hoverTimeout); |
| 142 | + |
| 143 | + // Remove test popup |
| 144 | + const testPopup = svg.querySelector('div[style*="background: red"]'); |
| 145 | + if (testPopup) { |
| 146 | + testPopup.remove(); |
| 147 | + console.log('Threadly: Test popup removed'); |
| 148 | + } |
| 149 | + |
| 150 | + if (popup) { |
| 151 | + console.log('Threadly: Removing popup'); |
| 152 | + popup.remove(); |
| 153 | + popup = null; |
| 154 | + } |
| 155 | + }); |
| 156 | + |
99 | 157 | // Add click handler (exact same as Claude) |
100 | 158 | svg.addEventListener('click', (event) => { |
101 | 159 | event.preventDefault(); |
102 | 160 | event.stopPropagation(); |
103 | 161 | console.log('Threadly: Sparkle clicked!'); |
| 162 | + |
| 163 | + // Test: Manually trigger hover event for debugging |
| 164 | + console.log('Threadly: Manually triggering hover test'); |
| 165 | + const testEvent = new MouseEvent('mouseenter', { |
| 166 | + bubbles: true, |
| 167 | + cancelable: true, |
| 168 | + view: window |
| 169 | + }); |
| 170 | + svg.dispatchEvent(testEvent); |
| 171 | + |
| 172 | + // If popup is visible, remove it and proceed with autonomous mode |
| 173 | + if (popup) { |
| 174 | + popup.remove(); |
| 175 | + popup = null; |
| 176 | + } |
| 177 | + |
104 | 178 | handleSparkleClick(); |
105 | 179 | }); |
106 | 180 |
|
|
111 | 185 | return svg; |
112 | 186 | } |
113 | 187 |
|
| 188 | + // Create mode selection popup |
| 189 | + function createModeSelectionPopup(sparkleElement) { |
| 190 | + console.log('Threadly: Creating mode selection popup'); |
| 191 | + console.log('Threadly: Sparkle element:', sparkleElement); |
| 192 | + |
| 193 | + try { |
| 194 | + // Remove existing popup if any |
| 195 | + const existingPopup = document.querySelector('.threadly-mode-popup'); |
| 196 | + if (existingPopup) { |
| 197 | + console.log('Threadly: Removing existing popup'); |
| 198 | + existingPopup.remove(); |
| 199 | + } |
| 200 | + |
| 201 | + // Create popup container |
| 202 | + console.log('Threadly: Creating popup div element'); |
| 203 | + const popup = document.createElement('div'); |
| 204 | + popup.className = 'threadly-mode-popup'; |
| 205 | + console.log('Threadly: Setting popup innerHTML'); |
| 206 | + popup.innerHTML = ` |
| 207 | + <div class="threadly-mode-option correction" data-mode="correction"> |
| 208 | + <span class="mode-icon">✏️</span> |
| 209 | + <span class="mode-text">CORRECT</span> |
| 210 | + </div> |
| 211 | + <div class="threadly-mode-option image" data-mode="image"> |
| 212 | + <span class="mode-icon">🎨</span> |
| 213 | + <span class="mode-text">IMAGE</span> |
| 214 | + </div> |
| 215 | + <div class="threadly-mode-option refine" data-mode="refine"> |
| 216 | + <span class="mode-icon">✨</span> |
| 217 | + <span class="mode-text">REFINE</span> |
| 218 | + </div> |
| 219 | + `; |
| 220 | + |
| 221 | + // Position popup relative to sparkle element |
| 222 | + console.log('Threadly: Setting sparkle element position'); |
| 223 | + sparkleElement.style.position = 'relative'; |
| 224 | + console.log('Threadly: Appending popup to sparkle element'); |
| 225 | + sparkleElement.appendChild(popup); |
| 226 | + console.log('Threadly: Popup appended to sparkle element'); |
| 227 | + console.log('Threadly: Popup element:', popup); |
| 228 | + console.log('Threadly: Sparkle element position:', sparkleElement.getBoundingClientRect()); |
| 229 | + |
| 230 | + // Show popup with animation |
| 231 | + setTimeout(() => { |
| 232 | + console.log('Threadly: Adding show class to popup'); |
| 233 | + popup.classList.add('show'); |
| 234 | + console.log('Threadly: Popup classes:', popup.className); |
| 235 | + console.log('Threadly: Popup position:', popup.getBoundingClientRect()); |
| 236 | + }, 10); |
| 237 | + |
| 238 | + // Add click handlers for each option |
| 239 | + popup.addEventListener('click', (e) => { |
| 240 | + const mode = e.target.closest('.threadly-mode-option')?.dataset.mode; |
| 241 | + if (mode) { |
| 242 | + handleModeSelection(mode, sparkleElement); |
| 243 | + popup.remove(); |
| 244 | + } |
| 245 | + }); |
| 246 | + |
| 247 | + // Hide popup when clicking outside |
| 248 | + const hidePopup = (e) => { |
| 249 | + if (!popup.contains(e.target) && !sparkleElement.contains(e.target)) { |
| 250 | + popup.remove(); |
| 251 | + document.removeEventListener('click', hidePopup); |
| 252 | + } |
| 253 | + }; |
| 254 | + |
| 255 | + setTimeout(() => { |
| 256 | + document.addEventListener('click', hidePopup); |
| 257 | + }, 100); |
| 258 | + |
| 259 | + return popup; |
| 260 | + } catch (error) { |
| 261 | + console.error('Threadly: Error creating popup:', error); |
| 262 | + return null; |
| 263 | + } |
| 264 | + } |
| 265 | + |
| 266 | + // Handle mode selection |
| 267 | + async function handleModeSelection(mode, sparkleElement) { |
| 268 | + console.log('Threadly: Mode selected:', mode); |
| 269 | + |
| 270 | + // Get current input text |
| 271 | + const textArea = document.querySelector('textarea, [contenteditable="true"]'); |
| 272 | + if (!textArea) { |
| 273 | + console.log('Threadly: No text area found'); |
| 274 | + return; |
| 275 | + } |
| 276 | + |
| 277 | + const currentText = textArea.value || textArea.textContent || textArea.innerText; |
| 278 | + if (!currentText || currentText.trim() === '') { |
| 279 | + console.log('Threadly: No text to process'); |
| 280 | + return; |
| 281 | + } |
| 282 | + |
| 283 | + // Visual feedback |
| 284 | + startClickAnimationSequence(sparkleElement); |
| 285 | + |
| 286 | + try { |
| 287 | + if (window.PromptRefiner) { |
| 288 | + const promptRefiner = new window.PromptRefiner(); |
| 289 | + await promptRefiner.initialize(); |
| 290 | + |
| 291 | + let refinedPrompt; |
| 292 | + const platform = detectCurrentPlatform(); |
| 293 | + |
| 294 | + switch (mode) { |
| 295 | + case 'correction': |
| 296 | + refinedPrompt = await promptRefiner.performGrammarCorrection(currentText); |
| 297 | + break; |
| 298 | + case 'image': |
| 299 | + refinedPrompt = await promptRefiner.refineImageGenerationPrompt(currentText, platform); |
| 300 | + break; |
| 301 | + case 'refine': |
| 302 | + refinedPrompt = await promptRefiner.refinePrompt(currentText, platform); |
| 303 | + break; |
| 304 | + } |
| 305 | + |
| 306 | + // Replace text in the input |
| 307 | + if (refinedPrompt) { |
| 308 | + if (textArea.tagName === 'TEXTAREA') { |
| 309 | + textArea.value = refinedPrompt; |
| 310 | + } else { |
| 311 | + textArea.textContent = refinedPrompt; |
| 312 | + } |
| 313 | + |
| 314 | + // Trigger input event to notify the platform |
| 315 | + textArea.dispatchEvent(new Event('input', { bubbles: true })); |
| 316 | + textArea.dispatchEvent(new Event('change', { bubbles: true })); |
| 317 | + } |
| 318 | + } |
| 319 | + } catch (error) { |
| 320 | + console.error('Threadly: Error processing prompt:', error); |
| 321 | + } |
| 322 | + } |
| 323 | + |
| 324 | + // Detect current platform |
| 325 | + function detectCurrentPlatform() { |
| 326 | + const url = window.location.href; |
| 327 | + if (url.includes('chat.openai.com') || url.includes('chatgpt.com')) { |
| 328 | + return 'chatgpt'; |
| 329 | + } else if (url.includes('claude.ai')) { |
| 330 | + return 'claude'; |
| 331 | + } else if (url.includes('aistudio.google.com')) { |
| 332 | + return 'ai-studio'; |
| 333 | + } else if (url.includes('gemini.google.com')) { |
| 334 | + return 'gemini'; |
| 335 | + } else if (url.includes('perplexity.ai')) { |
| 336 | + return 'perplexity'; |
| 337 | + } |
| 338 | + return 'chatgpt'; // default fallback |
| 339 | + } |
| 340 | + |
114 | 341 | // Handle sparkle click functionality with prompt refine feature (exact copy from Claude) |
115 | 342 | async function handleSparkleClick() { |
116 | 343 | console.log('Threadly: Sparkle clicked - prompt refine activated!'); |
|
427 | 654 | console.log('Threadly: Upload button parent:', uploadButton.parentElement); |
428 | 655 |
|
429 | 656 | // Create the sparkle icon |
| 657 | + console.log('Threadly: Creating sparkle icon'); |
430 | 658 | const sparkleIcon = createSparkleIcon(); |
| 659 | + console.log('Threadly: Sparkle icon created:', sparkleIcon); |
431 | 660 |
|
432 | 661 | // Style the sparkle icon |
433 | 662 | sparkleIcon.style.display = 'inline-block'; |
|
436 | 665 | sparkleIcon.style.top = '0'; |
437 | 666 | sparkleIcon.style.marginLeft = '0px'; |
438 | 667 | sparkleIcon.style.marginRight = '8px'; |
| 668 | + console.log('Threadly: Sparkle icon styled'); |
439 | 669 |
|
440 | 670 | // Find the main text bar container using multiple selectors for different page states |
441 | 671 | let textBarContainer = document.querySelector("body > app-root > ms-app > div > div > div.layout-wrapper.ng-tns-c895710572-0 > div > span > ms-prompt-switcher > ms-chunk-editor > section > footer > ms-prompt-input-wrapper > div > div > div"); |
|
503 | 733 | try { |
504 | 734 | textBarContainer.insertBefore(sparkleWrapper, uploadButtonWrapper); |
505 | 735 | console.log('Threadly: SUCCESS - Sparkle wrapper inserted before upload button wrapper'); |
| 736 | + console.log('Threadly: Sparkle icon in DOM:', document.querySelector('[data-threadly-sparkle="true"]')); |
| 737 | + console.log('Threadly: Sparkle icon position:', sparkleIcon.getBoundingClientRect()); |
506 | 738 | } catch (error) { |
507 | 739 | console.error('Threadly: Insert sparkle wrapper failed:', error); |
508 | 740 | // Fallback: append to text bar container |
509 | 741 | textBarContainer.appendChild(sparkleWrapper); |
510 | 742 | console.log('Threadly: FALLBACK - Sparkle wrapper appended to text bar container'); |
| 743 | + console.log('Threadly: Sparkle icon in DOM:', document.querySelector('[data-threadly-sparkle="true"]')); |
511 | 744 | } |
512 | 745 | } else { |
513 | 746 | // Try to find the parent container of the upload button and insert before it |
|
0 commit comments