您需要先安装一款用户样式管理器扩展(如 Stylus )后才能安装此样式。
您需要先安装一款用户样式管理器扩展(如 Stylus )后才能安装此样式。
您需要先安装一款用户样式管理器扩展(如 Stylus )后才能安装此样式。
您需要先安装一款用户样式管理器扩展后才能安装此样式。
您需要先安装一款用户样式管理器扩展后才能安装此样式。
您需要先安装一款用户样式管理器扩展后才能安装此样式。
(我已经安装了用户样式管理器,让我安装!)
Wrap lines
// ==UserScript==
// @name ChatGPT Pin Chats
// @namespace https://gf.qytechs.cn/en/users/1444872-tlbstation
// @version 1.8.0
// @description Add a pin button to each chat in ChatGPT's sidebar, making it easy to save important conversations. Pinned chats are displayed in the 'Pinned Chats' section at the bottom of the sidebar. Click pinned chats to reopen them, and you can also remove individual pins or clear all pinned chats.
// @icon https://i.ibb.co/jZ3HpwPk/pngwing-com.png
// @author TLBSTATION
// @match https://chatgpt.com/*
// @grant GM_setValue
// @grant GM_getValue
// @license MIT
// ==/UserScript==
(function () {
'use strict';
const STORAGE_KEY = 'pinnedChatsGPT';
function getPinnedChats() {
return JSON.parse(localStorage.getItem(STORAGE_KEY)) || [];
}
function savePinnedChats(chats) {
localStorage.setItem(STORAGE_KEY, JSON.stringify(chats));
}
function createPinnedSection() {
let pinnedContainer = document.querySelector('#pinned-chats');
if (!pinnedContainer) {
pinnedContainer = document.createElement('div');
pinnedContainer.id = 'pinned-chats';
pinnedContainer.style.padding = '10px';
pinnedContainer.style.marginBottom = '0';
pinnedContainer.style.borderBottom = '2px solid rgba(255, 255, 255, 0.2)';
//pinnedContainer.style.borderTop = '2px solid rgba(255, 255, 255, 0.2)';
pinnedContainer.style.color = 'white';
pinnedContainer.style.paddingLeft = '10px';
pinnedContainer.style.marginLeft = '-11px';
function insertPinnedSection() {
const targetDiv = document.querySelector('.flex-col.flex-1.transition-opacity.duration-500.relative.pr-3.overflow-y-auto');
if (targetDiv && targetDiv.parentElement) {
targetDiv.parentElement.insertBefore(pinnedContainer, targetDiv.nextSibling); // Insert after target div
return true;
}
return false;
}
// Try inserting immediately
if (!insertPinnedSection()) {
// If not found, keep checking until target div appears
const observer = new MutationObserver(() => {
if (insertPinnedSection()) observer.disconnect();
});
observer.observe(document.body, { childList: true, subtree: true });
}
}
return pinnedContainer;
}
function updatePinnedSection() {
const pinnedContainer = createPinnedSection();
const pinnedChats = getPinnedChats();
pinnedContainer.innerHTML = '';
// Header (Title + Clear Button)
const header = document.createElement('div');
header.style.display = 'flex';
header.style.alignItems = 'center';
header.style.justifyContent = 'space-between';
header.style.marginBottom = '10px';
const title = document.createElement('h3');
title.innerText = '📌 Pinned Chats';
title.style.fontSize = '16px';
title.style.fontWeight = 'bold';
title.style.margin = '0';
const clearButton = document.createElement('button');
clearButton.innerText = '❌';
clearButton.style.padding = '4px 4px';
clearButton.style.border = 'none';
clearButton.style.cursor = 'pointer';
clearButton.style.background = 'white';
clearButton.style.color = 'black';
clearButton.style.fontSize = '11px';
clearButton.style.borderRadius = '4px';
clearButton.style.transition = 'background 0.3s';
clearButton.addEventListener('mouseenter', () => {
clearButton.style.background = '#682828';
clearButton.style.color = 'white';
});
clearButton.addEventListener('mouseleave', () => {
clearButton.style.background = 'white';
clearButton.style.color = 'black';
});
clearButton.addEventListener('click', () => {
localStorage.removeItem(STORAGE_KEY);
updatePinnedSection();
addPinToChats(); // Ensure pins are re-checked
// Reset all pin button opacities in chat history
document.querySelectorAll('.pin-button').forEach(button => {
button.style.opacity = '0.5';
});
});
header.appendChild(title);
header.appendChild(clearButton);
pinnedContainer.appendChild(header);
// Pinned chats list
if (pinnedChats.length > 0) {
clearButton.style.display = 'block';
const list = document.createElement('ul');
list.style.listStyle = 'none';
list.style.padding = '0';
list.style.margin = '0';
// Set fixed height and make it scrollable
list.style.maxHeight = '200px'; // Adjust height as needed
list.style.overflowY = 'auto';
list.style.paddingRight = '5px'; // Prevents scrollbar from overlapping content
pinnedChats.forEach(chat => {
const chatItem = document.createElement('li');
chatItem.style.padding = '8px';
chatItem.style.marginBottom = '5px';
chatItem.style.cursor = 'pointer';
chatItem.style.borderRadius = '6px';
chatItem.style.background = 'rgba(255, 255, 255, 0.1)';
chatItem.style.transition = 'background 0.3s';
chatItem.style.display = 'flex';
chatItem.style.alignItems = 'center';
chatItem.addEventListener('mouseenter', () => {
chatItem.style.background = 'rgba(255, 255, 255, 0.2)';
});
chatItem.addEventListener('mouseleave', () => {
chatItem.style.background = 'rgba(255, 255, 255, 0.1)';
});
// Create clickable link
const chatLink = document.createElement('a');
chatLink.href = `https://chatgpt.com/c/${chat.id}`;
chatLink.innerText = chat.title;
chatLink.style.color = 'white';
chatLink.style.textDecoration = 'none';
chatLink.style.flexGrow = '1';
chatLink.style.whiteSpace = 'nowrap';
chatLink.style.overflow = 'hidden';
chatLink.style.textOverflow = 'ellipsis';
// Remove button
const removeButton = document.createElement('span');
removeButton.innerText = '❌';
removeButton.style.cursor = 'pointer';
removeButton.style.marginLeft = '10px';
removeButton.style.fontSize = '14px';
removeButton.style.opacity = '0';
removeButton.style.transition = 'opacity 0.3s';
chatItem.addEventListener('mouseenter', () => {
removeButton.style.opacity = '1';
});
chatItem.addEventListener('mouseleave', () => {
removeButton.style.opacity = '0';
});
removeButton.addEventListener('click', (event) => {
event.stopPropagation();
let updatedChats = getPinnedChats().filter(c => c.id !== chat.id);
savePinnedChats(updatedChats);
updatePinnedSection();
// Update pin button opacity in chat history
document.querySelectorAll('.pin-button').forEach(button => {
const parentChatItem = button.closest('li[data-testid^="history-item-"]');
if (parentChatItem) {
const chatID = parentChatItem.querySelector('a')?.getAttribute('href')?.split('/c/')[1];
if (chatID === chat.id) {
button.style.opacity = '0.5'; // Set opacity to indicate unpinned
}
}
});
});
chatItem.appendChild(chatLink);
chatItem.appendChild(removeButton);
list.appendChild(chatItem);
});
pinnedContainer.appendChild(list);
} else {
clearButton.style.display = 'none';
const emptyMessage = document.createElement('p');
emptyMessage.innerText = 'No pinned chats';
emptyMessage.style.color = 'rgba(255, 255, 255, 0.6)';
emptyMessage.style.fontSize = '14px';
pinnedContainer.appendChild(emptyMessage);
}
}
function addPinToChats() {
document.querySelectorAll('li[data-testid^="history-item-"]').forEach(chatItem => {
if (chatItem.querySelector('.pin-button')) return;
const chatTitleElement = chatItem.querySelector('a div');
if (!chatTitleElement) return;
const chatTitle = chatTitleElement.innerText.trim();
const chatID = chatItem.querySelector('a')?.getAttribute('href')?.split('/c/')[1];
const pinButton = document.createElement('span');
pinButton.className = 'pin-button';
pinButton.innerText = '📌';
pinButton.style.cursor = 'pointer';
pinButton.style.marginRight = '8px';
pinButton.style.fontSize = '16px';
pinButton.style.opacity = '0.5';
let pinnedChats = getPinnedChats();
if (pinnedChats.some(c => c.id === chatID)) pinButton.style.opacity = '1';
pinButton.addEventListener('click', (event) => {
event.stopPropagation(); // Prevents bubbling up
event.preventDefault(); // Ensures no accidental refresh or navigation
let pinnedChats = getPinnedChats();
if (pinnedChats.some(c => c.id === chatID)) {
pinnedChats = pinnedChats.filter(c => c.id !== chatID);
pinButton.style.opacity = '0.5';
} else {
pinnedChats.unshift({ title: chatTitle, id: chatID });
pinButton.style.opacity = '1';
}
savePinnedChats(pinnedChats);
updatePinnedSection();
});
chatTitleElement.parentElement.prepend(pinButton);
});
}
function observeSidebar() {
const observer = new MutationObserver(() => addPinToChats());
const sidebar = document.querySelector('nav');
if (sidebar) observer.observe(sidebar, { childList: true, subtree: true });
}
function init() {
addPinToChats();
updatePinnedSection();
observeSidebar();
}
function waitForElement(selector, callback) {
const observer = new MutationObserver((mutations, obs) => {
if (document.querySelector(selector)) {
callback();
obs.disconnect(); // Stop observing once the element is found
}
});
observer.observe(document.body, { childList: true, subtree: true });
}
// Start script after the required element appears
waitForElement(".relative.grow.overflow-hidden.whitespace-nowrap", init);
})();