🚀 三合一 AI 侧边导航栏。支持 Gemini、ChatGPT、DeepSeek。提供长对话索引、极速跳转与本地收藏功能。
// ==UserScript==
// @name AI 导航栏(Gemini & ChatGPT & deepseek)
// @namespace https://github.com/kuilei98/ai-conversation-sidebar
// @version 1.0.1.1205
// @description 🚀 三合一 AI 侧边导航栏。支持 Gemini、ChatGPT、DeepSeek。提供长对话索引、极速跳转与本地收藏功能。
// @author kuilei98
// @match https://gemini.google.com/*
// @match https://chatgpt.com/*
// @match https://chat.openai.com/*
// @match https://chat.deepseek.com/*
// @grant none
// @license MIT
// @run-at document-end
// ==/UserScript==
(function() {
'use strict';
// 平台配置
const PLATFORMS = {
'gemini': {
name: 'Gemini',
color: '#0b57d0',
highlightColor: 'rgba(11, 87, 208, 0.2)',
topOffset: '140px',
selectors: [
'user-query',
'.user-query',
'[data-test-id="user-query"]',
'div.user-query-container'
],
getText: (el) => el.innerText || el.textContent || ''
},
'chatgpt': {
name: 'ChatGPT',
color: '#10a37f',
highlightColor: 'rgba(16, 163, 127, 0.2)',
topOffset: '140px',
selectors: [
'[data-message-author-role="user"]',
'.group\\/conversation-turn:has([data-message-author-role="user"])'
],
getText: (el) => el.innerText || el.textContent || ''
},
'deepseek': {
name: 'DeepSeek',
color: '#4d8aff',
highlightColor: 'rgba(77, 138, 255, 0.2)',
topOffset: '100px',
customQueryList: () => {
const questions = [];
document.querySelectorAll('.ds-message:has(> .ds-markdown)').forEach(answerMsg => {
const parentDiv = answerMsg.parentElement;
const prevSibling = parentDiv ? parentDiv.previousElementSibling : null;
if (prevSibling) {
const questionMsg = prevSibling.querySelector('.ds-message');
if (questionMsg) questions.push(questionMsg);
}
});
return questions;
},
getText: (el) => el.innerText || el.textContent || ''
}
};
// 环境检测
const host = window.location.hostname;
let currentPlatform = null;
let siteKey = '';
if (host.includes('gemini.google')) {
currentPlatform = PLATFORMS.gemini;
siteKey = 'gemini';
} else if (host.includes('chatgpt') || host.includes('openai')) {
currentPlatform = PLATFORMS.chatgpt;
siteKey = 'chatgpt';
} else if (host.includes('deepseek')) {
currentPlatform = PLATFORMS.deepseek;
siteKey = 'deepseek';
} else {
return;
}
// 全局状态
const NAV_CONTAINER_ID = 'ai-nav-container-universal';
let currentChatId = '';
let cachedStars = {};
// 样式注入
const cssStyles = `
/* 导航容器 */
#${NAV_CONTAINER_ID} {
position: fixed;
top: ${currentPlatform.topOffset};
right: 15px;
width: auto;
max-height: 70vh;
display: flex;
flex-direction: column;
gap: 4px;
align-items: flex-end;
z-index: 2147483647;
pointer-events: none;
overflow-y: auto;
overflow-x: visible;
padding-right: 2px;
padding-bottom: 20px;
scrollbar-width: none;
}
#${NAV_CONTAINER_ID}::-webkit-scrollbar { display: none; }
/* 配色变量 */
:root {
--ai-bg: #ffffff;
--ai-border: rgba(0,0,0,0.08);
--ai-shadow: 0 2px 6px rgba(0,0,0,0.08);
--ai-shadow-hover: 0 8px 20px rgba(0,0,0,0.12);
--ai-text-primary: #1f1f1f;
--ai-text-secondary: #444746;
--ai-accent: ${currentPlatform.color};
--ai-star-inactive: #c4c7c5;
--ai-star-active: #fbbc04;
--ai-star-bg: #fff8e1;
--ai-star-text: #b06000;
}
@media (prefers-color-scheme: dark) {
:root {
--ai-bg: #1e1f20;
--ai-border: rgba(255,255,255,0.1);
--ai-shadow: 0 2px 6px rgba(0,0,0,0.4);
--ai-shadow-hover: 0 8px 24px rgba(0,0,0,0.6);
--ai-text-primary: #e3e3e3;
--ai-text-secondary: #c4c7c5;
--ai-star-inactive: #8e918f;
--ai-star-active: #fbbc04;
--ai-star-bg: #3f3a2c;
--ai-star-text: #fdd663;
}
}
${siteKey === 'chatgpt' ? `@media (prefers-color-scheme: dark) { :root { --ai-bg: #212121; } }` : ''}
/* 页面内索引样式 */
[data-ai-index]::before {
content: attr(data-ai-index);
display: inline-block;
font-family: Consolas, monospace;
font-weight: bold;
color: var(--ai-accent);
margin-right: 10px;
font-size: 1.1em;
opacity: 1;
user-select: none;
vertical-align: middle;
}
/* 胶囊按钮样式 */
.nav-capsule {
pointer-events: auto;
background-color: var(--ai-bg);
border: 1px solid var(--ai-border);
color: var(--ai-text-primary);
width: 34px;
height: 34px;
border-radius: 5px 0 0 5px;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
box-shadow: var(--ai-shadow);
cursor: pointer;
transition: width 0.25s cubic-bezier(0.2, 0, 0, 1), background-color 0.2s, box-shadow 0.2s;
overflow: hidden;
white-space: nowrap;
position: relative;
}
.nav-capsule:hover {
width: 280px;
padding: 0 12px;
justify-content: space-between;
background-color: var(--ai-bg);
box-shadow: var(--ai-shadow-hover);
z-index: 10000;
border-color: transparent;
border-radius: 5px 0 0 5px;
}
.capsule-index { font-weight: 700; font-size: 13px; color: var(--ai-accent); text-align: center; min-width: auto; }
.capsule-text { display: none; font-size: 13px; color: var(--ai-text-secondary); flex: 1; margin: 0 12px; overflow: hidden; text-overflow: ellipsis; text-align: left; }
.nav-capsule:hover .capsule-text { display: block; animation: fadeIn 0.2s forwards; }
.capsule-star { display: none; font-size: 16px; color: var(--ai-star-inactive); width: 18px; text-align: center; opacity: 0.4; transform: scale(0.9); transition: all 0.2s ease; }
.nav-capsule:hover .capsule-star { display: block; }
.capsule-star.unlocked { opacity: 1 !important; color: var(--ai-text-primary); transform: scale(1.1); }
.capsule-star.denied { animation: shake 0.3s ease; }
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
@keyframes shake { 0%, 100% { transform: translateX(0); } 25% { transform: translateX(-2px); } 75% { transform: translateX(2px); } }
.nav-capsule.starred { background-color: var(--ai-star-bg); border-color: transparent; }
.nav-capsule.starred .capsule-index { color: var(--ai-star-text); }
.nav-capsule.starred .capsule-star { display: block !important; opacity: 1 !important; color: var(--ai-star-active) !important; transform: scale(1); }
`;
const styleEl = document.createElement('style');
styleEl.textContent = cssStyles;
document.head.appendChild(styleEl);
// 功能逻辑循环
setInterval(() => {
try {
updateChatId();
ensureContainer();
} catch (e) { console.error("AI Nav Error:", e); }
}, 1000);
const scanInterval = siteKey === 'deepseek' ? 2000 : 1500;
setInterval(scanMessages, scanInterval);
// 更新当前会话ID
function updateChatId() {
const newPath = window.location.pathname;
if (newPath !== currentChatId) {
currentChatId = newPath;
const storageKey = `${siteKey}_stars_${currentChatId}`;
cachedStars = JSON.parse(localStorage.getItem(storageKey) || '{}');
const container = document.getElementById(NAV_CONTAINER_ID);
if(container) container.replaceChildren();
scanMessages(true);
}
}
// 生成唯一Key
function getMessageKey(text, occurrenceIndex) {
if (!text) return "empty_node";
return text.trim().substring(0, 50) + "_idx_" + occurrenceIndex;
}
// 收藏状态切换
function toggleStar(key, event, starElement, isCurrentlyStarred) {
if (!isCurrentlyStarred && !starElement.classList.contains('unlocked')) {
starElement.classList.add('unlocked');
starElement.classList.remove('denied');
void starElement.offsetWidth;
starElement.classList.add('denied');
return;
}
event.stopPropagation();
if (cachedStars[key]) delete cachedStars[key];
else cachedStars[key] = true;
const storageKey = `${siteKey}_stars_${currentChatId}`;
localStorage.setItem(storageKey, JSON.stringify(cachedStars));
scanMessages(true);
}
// 确保容器存在
function ensureContainer() {
if (!document.getElementById(NAV_CONTAINER_ID)) {
const container = document.createElement('div');
container.id = NAV_CONTAINER_ID;
document.documentElement.appendChild(container);
}
}
// 获取最佳查询列表
function getBestQueryList() {
if (currentPlatform.customQueryList) return currentPlatform.customQueryList();
let bestQueries = [];
for (let s of currentPlatform.selectors) {
const res = document.querySelectorAll(s);
if (res.length > bestQueries.length) bestQueries = res;
}
return bestQueries;
}
// 瞬间跳转与高亮
function handleJump(index) {
const bestQueries = getBestQueryList();
const target = bestQueries[index];
if (target) {
target.scrollIntoView({ behavior: 'instant', block: 'center' });
const originalTransition = target.style.transition;
const originalBg = target.style.backgroundColor;
target.style.transition = 'background-color 0.5s ease';
target.style.backgroundColor = currentPlatform.highlightColor;
setTimeout(() => {
target.style.backgroundColor = originalBg || 'transparent';
setTimeout(() => {
target.style.transition = originalTransition;
}, 500);
}, 1500);
}
}
// 扫描消息并渲染
function scanMessages(force = false) {
const container = document.getElementById(NAV_CONTAINER_ID);
if (!container) return;
const queries = getBestQueryList();
if (queries.length === 0 && !force) return;
const shouldRebuildNav = force || (queries.length !== container.children.length);
if (shouldRebuildNav) {
container.textContent = '';
}
const textFrequencyMap = {};
Array.from(queries).forEach((el, index) => {
// 注入页面内索引
const indexLabel = `Q${index + 1}`;
if (el.getAttribute('data-ai-index') !== indexLabel) {
el.setAttribute('data-ai-index', indexLabel);
}
if (!shouldRebuildNav) return;
let rawText = currentPlatform.getText(el) || `Question ${index + 1}`;
rawText = rawText.replace(/\s+/g, ' ').trim();
const currentCount = textFrequencyMap[rawText] || 0;
textFrequencyMap[rawText] = currentCount + 1;
const uniqueKey = getMessageKey(rawText, currentCount);
const shortText = rawText.length > 25 ? rawText.substring(0, 25) + '...' : rawText;
const isStarred = !!cachedStars[uniqueKey];
const capsule = document.createElement('div');
capsule.className = 'nav-capsule';
if (isStarred) capsule.classList.add('starred');
capsule.onmouseleave = () => {
const s = capsule.querySelector('.capsule-star');
if(s) s.classList.remove('unlocked');
};
capsule.onclick = () => handleJump(index);
const indexSpan = document.createElement('span');
indexSpan.className = 'capsule-index';
indexSpan.textContent = indexLabel;
const textSpan = document.createElement('span');
textSpan.className = 'capsule-text';
textSpan.textContent = shortText;
const starIcon = document.createElement('span');
starIcon.className = 'capsule-star';
starIcon.textContent = isStarred ? '★' : '☆';
starIcon.title = isStarred ? '取消收藏' : '点击两次以收藏';
starIcon.onclick = (e) => toggleStar(uniqueKey, e, starIcon, isStarred);
capsule.appendChild(indexSpan);
capsule.appendChild(textSpan);
capsule.appendChild(starIcon);
container.appendChild(capsule);
});
}
})();