Index: ChatGPT

為 ChatGPT 增加側邊提問索引列

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

You will need to install an extension such as Tampermonkey to install this script.

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name Index: ChatGPT
// @match https://chatgpt.com/*
// @icon https://chatgpt.com/favicon.ico
// @version 1.0
// @description 為 ChatGPT 增加側邊提問索引列
// @author Kamiya Minoru
// @grant GM_addStyle
// @license MIT
// @namespace https://chatgpt.com/
// ==/UserScript==

(function() {
    'use strict';

    GM_addStyle(`
        #custom-nav-radar {
            position: fixed;
            right: 20px; top: 10%; height: 80%; width: 40px; z-index: 9999;
            display: flex; flex-direction: column;
            justify-content: center; align-items: center;
            pointer-events: none;}

        .user-message-bubble-color {scroll-margin-top: 80px;}

        .nav-dot-item {
            flex: 0 0 auto;
            width: 14px; height: 8px;
            background-color: rgba(128, 128, 128, 0.4);
            border-radius: 4px; margin: 4px 0; cursor: pointer; pointer-events: auto;
            transition: width 0.2s, background-color 0.2s, transform 0.2s;
            position: relative;}

        .nav-dot-item::after {
            content: '';
            position: absolute;
            top: -5px; bottom: -5px; left: -10px; right: -10px;}

        .nav-dot-item:hover {
            background-color: #10a37f;
            width: 22px;
            transform: translateX(-2px);}

        .nav-tooltip {
            position: absolute; right: 40px; top: 50%; transform: translateY(-50%);
            background: #202123; color: #fff;
            padding: 15px; border-radius: 10px;
            width: 450px; max-height: 250px; overflow: hidden;
            font-size: 14px; line-height: 1.5;
            display: none; box-shadow: 0 10px 30px rgba(0,0,0,0.6);
            border: 1px solid #4e4e4e; pointer-events: none;
            white-space: pre-wrap; word-wrap: break-word; z-index: 10001;}

        .nav-dot-item:hover .nav-tooltip {display: block;}`);

    const radarContainer = document.createElement('div');
    radarContainer.id = 'custom-nav-radar';
    document.body.appendChild(radarContainer);

    let lastMessageCount = 0;

    function refreshNav() {
        const userMessages = document.querySelectorAll('.user-message-bubble-color');

        if (userMessages.length === lastMessageCount) return;

        lastMessageCount = userMessages.length;
        radarContainer.innerHTML = '';

        const dynamicMargin = userMessages.length > 25 ? '2px' : '5px';

        userMessages.forEach((msg, idx) => {
            const dot = document.createElement('div');
            dot.className = 'nav-dot-item';
            dot.style.margin = `${dynamicMargin} 0`;

            const tooltip = document.createElement('div');
            tooltip.className = 'nav-tooltip';
            tooltip.innerText = `提問 #${idx + 1}\n\n${msg.innerText.trim()}`;
            dot.appendChild(tooltip);

            dot.onclick = (e) => {
                e.preventDefault();
                msg.scrollIntoView({ behavior: 'smooth', block: 'start' });
            };

            radarContainer.appendChild(dot);
        });
    }

    let timer;
    const observer = new MutationObserver(() => {
        clearTimeout(timer);
        timer = setTimeout(refreshNav, 800); // 緩衝
    });

    observer.observe(document.body, { childList: true, subtree: true });

    // 初始載入
    setTimeout(refreshNav, 2000);

    // 定期巡檢(應對 SPA 路由切換)
    setInterval(refreshNav, 3000);
})();