Last active
January 25, 2026 15:05
-
-
Save jricardo27/ceaf163312d4e663efdb07eb6dbeeeb9 to your computer and use it in GitHub Desktop.
Extract Gemini Code Assist comments - Tampermonkey
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==UserScript== | |
| // @name Gemini Comment Extractor (Thread Support) | |
| // @namespace http://tampermonkey.net/ | |
| // @version 2.2 | |
| // @description Extract the latest Gemini bot comment from each thread with diff formatting | |
| // @author Antigravity | |
| // @match https://github.com/*/*/pull/* | |
| // @grant none | |
| // ==/UserScript== | |
| (function() { | |
| 'use strict'; | |
| const UI_CONFIG = { | |
| primaryColor: '#1a73e8', | |
| backgroundColor: '#ffffff', | |
| textColor: '#24292f', | |
| border: '1px solid #d0d7de', | |
| fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif' | |
| }; | |
| /** | |
| * Parsing Logic | |
| */ | |
| const Extractor = { | |
| getFileName(threadContainer) { | |
| const fileLink = threadContainer.querySelector('summary a.wb-break-all'); | |
| return fileLink ? fileLink.innerText.trim() : 'Unknown File'; | |
| }, | |
| getLines(threadContainer) { | |
| const start = threadContainer.querySelector('.js-multi-line-preview-start'); | |
| const end = threadContainer.querySelector('.js-multi-line-preview-end'); | |
| if (start && end) { | |
| return `${start.innerText.trim()} - ${end.innerText.trim()}`; | |
| } | |
| const lineNums = threadContainer.querySelectorAll('td[data-line-number]'); | |
| if (lineNums.length > 0) { | |
| return lineNums[lineNums.length - 1].getAttribute('data-line-number'); | |
| } | |
| return 'N/A'; | |
| }, | |
| getPriority(commentBody) { | |
| if (!commentBody) return 'Medium'; | |
| // Look for the priority SVG image in the specific comment body | |
| const img = commentBody.querySelector('img[src*="priority"], img[data-canonical-src*="priority"]'); | |
| if (img) { | |
| return img.getAttribute('alt') || img.getAttribute('data-canonical-src').split('/').pop().replace('.svg', ''); | |
| } | |
| return 'Medium'; | |
| }, | |
| formatCommentWithDiffs(commentBody) { | |
| if (!commentBody) return ''; | |
| const bodyClone = commentBody.cloneNode(true); | |
| // Handle Suggested Change blocks | |
| const suggestedChanges = bodyClone.querySelectorAll('.js-suggested-changes-blob'); | |
| suggestedChanges.forEach(blob => { | |
| const rows = blob.querySelectorAll('tr'); | |
| let diffText = '\n```diff\n'; | |
| rows.forEach(row => { | |
| const codeCell = row.querySelector('.blob-code-inner'); | |
| if (!codeCell) return; | |
| let prefix = ' '; | |
| if (codeCell.classList.contains('blob-code-marker-deletion')) prefix = '-'; | |
| if (codeCell.classList.contains('blob-code-marker-addition')) prefix = '+'; | |
| diffText += `${prefix}${codeCell.innerText}\n`; | |
| }); | |
| diffText += '```\n'; | |
| blob.outerHTML = diffText; | |
| }); | |
| return bodyClone.innerText.trim(); | |
| }, | |
| isGeminiBot(commentGroup) { | |
| const author = commentGroup.querySelector('.author'); | |
| return author && author.innerText.toLowerCase().includes('gemini-code-assist'); | |
| }, | |
| process() { | |
| // Find all review threads | |
| const threads = document.querySelectorAll('.review-thread-component'); | |
| const results = []; | |
| threads.forEach(thread => { | |
| // Skip if marked as Outdated | |
| const isOutdated = thread.querySelector('.Label--warning') && thread.querySelector('.Label--warning').innerText.includes('Outdated'); | |
| if (isOutdated) return; | |
| // Find all comments in this thread (the conversation history) | |
| const allCommentGroups = Array.from(thread.querySelectorAll('.timeline-comment-group')); | |
| // Filter for comments actually made by the Gemini bot | |
| const botComments = allCommentGroups.filter(group => this.isGeminiBot(group)); | |
| if (botComments.length === 0) return; | |
| // Requirement: Use the LAST comment from him in the interaction | |
| const targetCommentGroup = botComments[botComments.length - 1]; | |
| const commentBody = targetCommentGroup.querySelector('.comment-body'); | |
| const rawBodyText = commentBody ? commentBody.innerText.trim() : ''; | |
| // Filter out the general summary bot post | |
| if (rawBodyText.toLowerCase().includes('summary of changes')) return; | |
| const fileName = this.getFileName(thread); | |
| const lines = this.getLines(thread); | |
| const priority = this.getPriority(commentBody); | |
| const formattedComment = this.formatCommentWithDiffs(commentBody); | |
| results.push({ | |
| fileName, | |
| lines, | |
| priority, | |
| comment: formattedComment | |
| }); | |
| }); | |
| return results; | |
| } | |
| }; | |
| /** | |
| * UI Implementation | |
| */ | |
| function createResultDisplay(data) { | |
| let existing = document.getElementById('gemini-results-container'); | |
| if (existing) existing.remove(); | |
| const container = document.createElement('div'); | |
| container.id = 'gemini-results-container'; | |
| Object.assign(container.style, { | |
| margin: '20px', padding: '20px', backgroundColor: UI_CONFIG.backgroundColor, | |
| border: UI_CONFIG.border, borderRadius: '8px', boxShadow: '0 4px 12px rgba(0,0,0,0.1)', | |
| fontFamily: UI_CONFIG.fontFamily, color: UI_CONFIG.textColor, clear: 'both' | |
| }); | |
| const header = document.createElement('div'); | |
| header.innerHTML = `<h3 style="margin:0 0 15px 0; border-bottom: 2px solid ${UI_CONFIG.primaryColor}; padding-bottom: 5px;"> | |
| Gemini Latest Feedback (${data.length}) | |
| <button id="close-gemini-results" style="float:right; border:none; background:none; cursor:pointer; font-size:18px;">×</button> | |
| </h3>`; | |
| container.appendChild(header); | |
| if (data.length === 0) { | |
| container.innerHTML += '<p style="color: #666;">No active Gemini bot replies found in threads.</p>'; | |
| } else { | |
| const list = document.createElement('div'); | |
| data.forEach(item => { | |
| const block = document.createElement('div'); | |
| Object.assign(block.style, { | |
| marginBottom: '15px', padding: '10px', backgroundColor: '#f6f8fa', | |
| border: '1px solid #d0d7de', borderRadius: '6px' | |
| }); | |
| block.innerHTML = ` | |
| <div style="font-weight: 600; color: ${UI_CONFIG.primaryColor};">File: ${item.fileName}</div> | |
| <div style="font-size: 0.9em; margin: 4px 0;"><strong>Lines:</strong> ${item.lines} | <strong>Priority:</strong> <span style="text-transform: uppercase;">${item.priority}</span></div> | |
| <div style="margin-top: 8px; white-space: pre-wrap; font-size: 13px; background: #fff; padding: 12px; border-radius: 4px; border: 1px solid #eee; line-height: 1.5;">${item.comment}</div> | |
| `; | |
| list.appendChild(block); | |
| }); | |
| container.appendChild(list); | |
| } | |
| document.body.appendChild(container); | |
| document.getElementById('close-gemini-results').onclick = () => container.remove(); | |
| container.scrollIntoView({ behavior: 'smooth' }); | |
| } | |
| function injectFloatingButton() { | |
| if (document.getElementById('gemini-floating-btn')) return; | |
| const btn = document.createElement('button'); | |
| btn.id = 'gemini-floating-btn'; btn.innerText = 'Extract Gemini'; | |
| Object.assign(btn.style, { | |
| position: 'fixed', bottom: '20px', right: '20px', zIndex: '9999', | |
| padding: '12px 24px', backgroundColor: UI_CONFIG.primaryColor, color: 'white', | |
| border: 'none', borderRadius: '50px', cursor: 'pointer', fontWeight: 'bold', | |
| boxShadow: '0 4px 15px rgba(26, 115, 232, 0.4)', transition: 'transform 0.2s', | |
| fontFamily: UI_CONFIG.fontFamily | |
| }); | |
| btn.onmouseover = () => btn.style.transform = 'scale(1.05)'; | |
| btn.onmouseout = () => btn.style.transform = 'scale(1)'; | |
| btn.onclick = (e) => { | |
| e.preventDefault(); | |
| createResultDisplay(Extractor.process()); | |
| }; | |
| document.body.appendChild(btn); | |
| } | |
| const observer = new MutationObserver(() => injectFloatingButton()); | |
| observer.observe(document.body, { childList: true, subtree: true }); | |
| injectFloatingButton(); | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment