// ==UserScript==
// @name GitHub AI 代码分析助手
// @namespace http://tampermonkey.net/
// @version 2.8
// @description:en Add an AI-powered code analysis button for GitHub repositories, supporting intelligent code interpretation via zread.ai and deepwiki.com
// @description:zh-CN 为 GitHub 仓库添加 AI 驱动的代码分析按钮,支持 zread.ai 和 deepwiki.com 智能解读代码
// @description:zh-TW 為 GitHub 倉庫添加 AI 驅動的代碼分析按鈕,支援 zread.ai 與 deepwiki.com 智能解讀代碼
// @match https://github.com/*/*
// @grant none
// @license MIT
// @description 为 GitHub 仓库添加 AI 驱动的代码分析按钮,支持 zread.ai 和 deepwiki.com 智能解读代码
// @downloadURL https://update.greasyfork.icu/scripts/545686/GitHub%20AI%20%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90%E5%8A%A9%E6%89%8B.user.js
// @updateURL https://update.greasyfork.icu/scripts/545686/GitHub%20AI%20%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90%E5%8A%A9%E6%89%8B.meta.js
// ==/UserScript==
(function () {
'use strict';
function insertButton() {
try {
// 提取用户名和仓库名
const pathParts = window.location.pathname.split('/').filter(Boolean);
if (pathParts.length < 2) {
console.log('AI Code Analysis: Not a repository page');
return;
}
const user = pathParts[0];
const repo = pathParts[1];
// 只在仓库主页、Code 页面插入
const subPath = pathParts[2] || '';
if (subPath && subPath !== 'tree' && subPath !== 'blob') {
console.log('AI Code Analysis: Not on main repository page');
return;
}
// 避免重复插入
if (document.querySelector('#zread-ai-btn') || document.querySelector('#code-reader-dropdown')) {
console.log('AI Code Analysis: Button already exists');
return;
}
// 更简单的策略:查找所有可能的按钮容器
let targetContainer = null;
// 方法1: 直接查找 ul.pagehead-actions (这是最正确的容器)
targetContainer = document.querySelector('ul.pagehead-actions');
if (targetContainer) {
// 创建li包装
const li = document.createElement('li');
// 创建按钮组容器
const btnGroup = document.createElement('div');
btnGroup.className = 'BtnGroup d-flex';
btnGroup.style.marginLeft = '8px';
// 创建主按钮
const mainBtn = document.createElement('button');
mainBtn.id = 'zread-ai-btn';
mainBtn.type = 'button';
mainBtn.className = 'btn btn-sm BtnGroup-item';
mainBtn.innerHTML = '🤖 AI Analysis';
// GitHub 原生按钮样式
mainBtn.style.cssText = `
position: relative;
display: inline-block;
padding: 5px 16px;
font-size: 12px;
font-weight: 500;
line-height: 20px;
white-space: nowrap;
vertical-align: middle;
cursor: pointer;
user-select: none;
background-repeat: repeat-x;
background-position: -1px -1px;
background-size: 110% 110%;
border: 1px solid rgba(31,35,40,0.15);
border-radius: 6px 0 0 6px;
appearance: none;
color: #24292f;
background-color: #f6f8fa;
background-image: linear-gradient(180deg,#f9fbfc,#f6f8fa 90%);
box-shadow: rgba(31, 35, 40, 0.04) 0px 1px 0px, rgba(255, 255, 255, 0.25) 0px 1px 0px inset;
`;
// 创建下拉按钮
const dropdownBtn = document.createElement('button');
dropdownBtn.type = 'button';
dropdownBtn.className = 'btn btn-sm BtnGroup-item px-2';
dropdownBtn.innerHTML = ``;
dropdownBtn.style.cssText = `
position: relative;
display: inline-block;
padding: 5px 8px;
font-size: 12px;
font-weight: 500;
line-height: 20px;
white-space: nowrap;
vertical-align: middle;
cursor: pointer;
user-select: none;
background-repeat: repeat-x;
background-position: -1px -1px;
background-size: 110% 110%;
border: 1px solid rgba(31,35,40,0.15);
border-radius: 0 6px 6px 0;
border-left: 0;
appearance: none;
color: #24292f;
background-color: #f6f8fa;
background-image: linear-gradient(180deg,#f9fbfc,#f6f8fa 90%);
box-shadow: rgba(31, 35, 40, 0.04) 0px 1px 0px, rgba(255, 255, 255, 0.25) 0px 1px 0px inset;
`;
// 创建下拉菜单
const dropdown = document.createElement('div');
dropdown.id = 'code-reader-dropdown';
dropdown.style.cssText = `
position: absolute;
top: 100%;
right: 0;
z-index: 100;
width: 200px;
margin-top: 5px;
background-color: #ffffff;
border: 1px solid rgba(31,35,40,0.15);
border-radius: 6px;
box-shadow: 0 8px 24px rgba(31,35,40,0.12);
display: none;
`;
// 菜单选项
const options = [
{
name: 'zread.ai',
url: 'https://zread.ai',
icon: '',
desc: 'Powered by Z.ai'
},
{
name: 'deepwiki.com',
url: 'https://deepwiki.com',
icon: '',
desc: 'Powered by Devin.ai'
},
{
name: 'gitingest.com',
url: 'https://gitingest.com',
icon: '',
desc: 'Code repository digest'
},
{
name: 'context7.com',
url: 'https://context7.com',
icon: '',
desc: 'Powered by Context7'
},
{
name: 'gitpodcast.com',
url: 'https://www.gitpodcast.com',
icon: '',
desc: 'Convert repo to podcast'
},
{
name: 'gitdiagram.com',
url: 'https://gitdiagram.com',
icon: '',
desc: 'Quickly visualizing projects'
},
{
name: 'gitmcp.io',
url: 'https://gitmcp.io',
icon: '',
desc: 'Remote MCP integration'
},
{
name: 'repomix.com',
url: 'https://repomix.com',
icon: '',
desc: 'Pack repo into single file'
}
];
options.forEach(option => {
const menuItem = document.createElement('a');
menuItem.href = '#';
menuItem.innerHTML = `
${option.icon}
${option.name}
${option.desc}
`;
menuItem.style.cssText = `
display: block;
padding: 12px 16px;
color: #24292f;
text-decoration: none;
border-bottom: 1px solid rgba(31,35,40,0.06);
font-size: 14px;
line-height: 20px;
transition: background-color 0.2s ease;
`;
menuItem.addEventListener('mouseenter', function () {
this.style.backgroundColor = '#f6f8fa';
});
menuItem.addEventListener('mouseleave', function () {
this.style.backgroundColor = 'transparent';
});
menuItem.addEventListener('click', function (e) {
e.preventDefault();
let targetUrl;
if (option.name === 'context7.com') {
// Context7 需要将用户名和仓库名转换为小写
targetUrl = `${option.url}/${user.toLowerCase()}/${repo.toLowerCase()}`;
} else if (option.name === 'repomix.com') {
// Repomix 使用查询参数格式
targetUrl = `${option.url}/?repo=https://github.com/${user}/${repo}`;
} else {
targetUrl = `${option.url}/${user}/${repo}`;
}
window.open(targetUrl, '_blank');
dropdown.style.display = 'none';
});
dropdown.appendChild(menuItem);
});
// 默认点击事件 (使用 zread.ai)
mainBtn.addEventListener('click', function (e) {
e.preventDefault();
window.open(`https://zread.ai/${user}/${repo}`, '_blank');
});
// 下拉按钮点击事件
dropdownBtn.addEventListener('click', function (e) {
e.preventDefault();
e.stopPropagation();
const isVisible = dropdown.style.display === 'block';
dropdown.style.display = isVisible ? 'none' : 'block';
});
// 点击其他地方隐藏下拉菜单
document.addEventListener('click', function (e) {
if (!btnGroup.contains(e.target)) {
dropdown.style.display = 'none';
}
});
// 按钮悬停效果
[mainBtn, dropdownBtn].forEach(btn => {
btn.addEventListener('mouseenter', function () {
this.style.backgroundColor = '#f3f4f6';
this.style.borderColor = 'rgba(31,35,40,0.25)';
});
btn.addEventListener('mouseleave', function () {
this.style.backgroundColor = '#f6f8fa';
this.style.borderColor = 'rgba(31,35,40,0.15)';
});
});
// 组装元素
btnGroup.appendChild(mainBtn);
btnGroup.appendChild(dropdownBtn);
btnGroup.appendChild(dropdown);
btnGroup.style.position = 'relative';
li.appendChild(btnGroup);
targetContainer.appendChild(li);
console.log('AI Code Analysis: Button group inserted into pagehead-actions!');
return;
}
// 方法2: 查找 Star 按钮并找到其容器
const starButtons = document.querySelectorAll('[aria-label*="Star"]');
for (let starBtn of starButtons) {
if (starBtn.textContent && starBtn.textContent.includes('Star')) {
targetContainer = starBtn.closest('div[class*="d-flex"], div[data-view-component="true"]');
if (targetContainer) break;
}
}
// 方法3: 查找包含 "Fork" 文本的按钮
if (!targetContainer) {
const forkButtons = document.querySelectorAll('*');
for (let element of forkButtons) {
if (element.textContent && element.textContent.trim() === 'Fork' && element.tagName === 'BUTTON') {
targetContainer = element.parentElement?.parentElement;
if (targetContainer) break;
}
}
}
// 方法4: 查找包含 "Watch" 文本的按钮
if (!targetContainer) {
const watchButtons = document.querySelectorAll('*');
for (let element of watchButtons) {
if (element.textContent && element.textContent.includes('Watch') && element.tagName === 'BUTTON') {
targetContainer = element.closest('div');
if (targetContainer && targetContainer.querySelector('[aria-label*="Star"]')) {
break;
}
}
}
}
if (!targetContainer) {
console.log('AI Code Analysis: Could not find target container');
return;
}
console.log('AI Code Analysis: Found target container:', targetContainer);
// 创建按钮(用于非 ul.pagehead-actions 容器)
const btn = document.createElement('button');
btn.id = 'zread-ai-btn-fallback';
btn.type = 'button';
btn.innerHTML = '🤖 AI Analysis';
// 模拟 GitHub 按钮样式
btn.style.cssText = `
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
padding: 5px 16px;
font-size: 14px;
font-weight: 600;
line-height: 20px;
white-space: nowrap;
vertical-align: middle;
cursor: pointer;
user-select: none;
border: 1px solid #0969da;
border-radius: 6px;
margin-left: 8px;
background-color: #0969da;
color: #ffffff;
text-decoration: none;
transition: 80ms cubic-bezier(0.65, 0, 0.35, 1);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif;
box-shadow: 0 1px 0 rgba(31, 35, 40, 0.1);
`;
// 添加悬停效果
btn.addEventListener('mouseenter', function () {
this.style.backgroundColor = '#0860ca';
this.style.borderColor = '#0860ca';
this.style.transform = 'translateY(-1px)';
this.style.boxShadow = '0 3px 6px rgba(9, 105, 218, 0.15)';
});
btn.addEventListener('mouseleave', function () {
this.style.backgroundColor = '#0969da';
this.style.borderColor = '#0969da';
this.style.transform = 'translateY(0)';
this.style.boxShadow = '0 1px 0 rgba(31, 35, 40, 0.1)';
});
// 点击事件
btn.addEventListener('click', function (e) {
e.preventDefault();
window.open(`https://zread.ai/${user}/${repo}`, '_blank');
});
// 插入按钮
targetContainer.appendChild(btn);
console.log('AI Code Analysis: Fallback button inserted successfully!');
} catch (error) {
console.error('AI Code Analysis: Error inserting button:', error);
}
}
// 使用更健壮的页面监听
let lastUrl = location.href;
// 页面变化监听
const observer = new MutationObserver((mutations) => {
const currentUrl = location.href;
if (currentUrl !== lastUrl) {
lastUrl = currentUrl;
console.log('AI Code Analysis: URL changed to:', currentUrl);
setTimeout(insertButton, 1500);
}
// 检查是否有新的按钮容器出现
for (let mutation of mutations) {
if (mutation.addedNodes) {
for (let node of mutation.addedNodes) {
if (node.nodeType === 1 && (node.querySelector && node.querySelector('[aria-label*="Star"]'))) {
setTimeout(insertButton, 500);
break;
}
}
}
}
});
observer.observe(document.body, {
childList: true,
subtree: true,
attributes: false
});
// 多次尝试初始化
document.addEventListener('DOMContentLoaded', () => {
setTimeout(insertButton, 1000);
});
setTimeout(insertButton, 1000);
setTimeout(insertButton, 2000);
setTimeout(insertButton, 3000);
// 如果页面已经加载完成
if (document.readyState === 'complete' || document.readyState === 'interactive') {
setTimeout(insertButton, 500);
}
})();