ChatGPT Pin Chats

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.

目前為 2025-03-12 提交的版本,檢視 最新版本

// ==UserScript==
// @name         ChatGPT Pin Chats
// @namespace    https://gf.qytechs.cn/en/users/1444872-tlbstation
// @version      1.8.2
// @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.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 not found, keep checking until target div appears
            if (!insertPinnedSection()) {
                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();
                event.preventDefault();

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

    // New: Monitor the sidebar so that if it's closed and reopened, we reinitialize the pinned section.
    function monitorSidebar() {
        const observer = new MutationObserver(() => {
            const sidebar = document.querySelector('nav');
            if (sidebar && !document.querySelector('#pinned-chats')) {
                init();
            }
        });
        observer.observe(document.body, { childList: true, subtree: true });
    }

    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 the initialization process after the sidebar loads
    waitForElement("nav", () => {
        init();
        monitorSidebar();
    });
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址