Skip to content

Instantly share code, notes, and snippets.

@jricardo27
Last active January 25, 2026 15:05
Show Gist options
  • Select an option

  • Save jricardo27/ceaf163312d4e663efdb07eb6dbeeeb9 to your computer and use it in GitHub Desktop.

Select an option

Save jricardo27/ceaf163312d4e663efdb07eb6dbeeeb9 to your computer and use it in GitHub Desktop.
Extract Gemini Code Assist comments - Tampermonkey
// ==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;">&times;</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