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