ChatGPT Question Navigation Sidebar v2

Lấy cảm hứng từ "ChatGPT Question Navigation sidebar" của okokdi, nhưng viết lại hoàn toàn bằng mã mới — siêu nhanh, mượt nhẹ, hiệu ứng đẹp vượt trội (Inspired by "ChatGPT Question Navigation sidebar" by okokdi, entirely rewritten with new code — ultra-fast, lightweight, with significantly improved, elegant effects.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name        ChatGPT Question Navigation Sidebar v2
// @namespace   npm/chatgpt-question-navigator
// @version     2.1
// @author      Pitroytech
// @description Lấy cảm hứng từ "ChatGPT Question Navigation sidebar" của okokdi, nhưng viết lại hoàn toàn bằng mã mới — siêu nhanh, mượt nhẹ, hiệu ứng đẹp vượt trội (Inspired by "ChatGPT Question Navigation sidebar" by okokdi, entirely rewritten with new code — ultra-fast, lightweight, with significantly improved, elegant effects.
// @match       https://chatgpt.com/**
// @grant       none
// ==/UserScript==


(function () {
  'use strict';

  // ====== CẤU HÌNH (CONFIG) ======
  // Tìm icon sidebar để thay <span class="toggle-btn">✮⋆˙</span>
  // Find sidebar icon to replace <span class="toggle-btn">✮⋆˙</span>

  // Độ trong suốt khi sidebar ĐANG THU GỌN và KHÔNG hover
  // Opacity when sidebar is COLLAPSED and NOT hovering
  // 0 = hoàn toàn trong suốt (fully transparent), 1 = đục hoàn toàn (fully opaque)
  const COLLAPSED_OPACITY = 0.35;

  // Thời gian ép sidebar "dính cuối" sau khi khởi tạo (ms)
  // Time to force sidebar "stick to bottom" after initialization (ms)
  // Gợi ý: 300–600ms (càng thấp thì càng ít can thiệp)
  // Suggestion: 300–600ms (lower = less intrusive)
  const FORCE_STICK_BOTTOM_MS = 500;
  // =====================

  // Các hằng số hệ thống (System constants)
  const DOM_MARK = 'data-chatgpt-question-directory';
  const CHAT_LIST_EL_CLASS = 'flex flex-col text-sm';
  const isSharePage = location.pathname.startsWith('/share/');
  const scrollMarginTop = 56;
  const RIGHT_OFFSET_PX = 12; // Sidebar luôn neo cách mép phải 12px (Sidebar always anchored 12px from right edge)
  const TOP_MIN_MARGIN = 20;  // Chặn kéo vượt viền trên (Prevent dragging beyond top boundary)
  const BOTTOM_MIN_MARGIN = 20; // Chặn kéo vượt viền dưới (Prevent dragging beyond bottom boundary)
  const DRAG_THRESHOLD_PX = 3; // Ngưỡng di chuyển để coi là kéo (Movement threshold to consider as dragging)

  // ====== CÁC HÀM TRỢ GIÚP (HELPER FUNCTIONS) ======

  // Tìm container chứa các tin nhắn chat (Find chat messages container)
  function queryChatContainer() {
    const main = document.querySelector('main');
    return main?.querySelector('.' + CHAT_LIST_EL_CLASS.split(' ').join('.'));
  }

  // Lấy danh sách các phần tử câu hỏi (chỉ lấy index chẵn)
  // Get list of question elements (only even indexes)
  function queryQuestionEls() {
    const container = queryChatContainer();
    if (!container) return [];

    return Array.from(container.children)
      .filter(child => child.hasAttribute('data-testid'))
      .filter((_, index) => index % 2 === 0); // Chỉ lấy câu hỏi (index chẵn) / Only questions (even index)
  }

  // Trích xuất nội dung text của các câu hỏi
  // Extract text content of questions
  function getQuestions() {
    const questionEls = queryQuestionEls();
    return questionEls.map(el => {
      const textEl = el.querySelector('[data-message-author-role]');
      return textEl?.innerText || '';
    }).filter(Boolean); // Loại bỏ các giá trị rỗng (Remove empty values)
  }

  // Lấy ID cuộc hội thoại từ URL (Get conversation ID from URL)
  function getConversationIdByUrl() {
    const match = location.pathname.match(/\/c\/(.*)/);
    return match?.[1] || null;
  }

  // ====== TẠO GIAO DIỆN SIDEBAR (CREATE SIDEBAR UI) ======
  function createSidebar() {
    const sidebar = document.createElement('div');
    sidebar.setAttribute(DOM_MARK, '');
    sidebar.innerHTML = `
      <style>
        /* Styles cho sidebar chính (Main sidebar styles) */
        [${DOM_MARK}] {
          position: fixed;
          top: 10vh;
          right: ${RIGHT_OFFSET_PX}px;
          padding: 12px;
          border-radius: 8px;
          background: rgba(17, 24, 39, 0.95);
          backdrop-filter: blur(10px);
          border: 1px solid rgba(255, 255, 255, 0.1);
          box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
          transition: all 0.65s cubic-bezier(0.4, 0, 0.2, 1);
          z-index: 1000;
          max-width: 300px;
          font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
          opacity: 1; /* Mặc định hiển thị rõ (Default fully visible) */
        }

        /* Trạng thái đang kéo (Dragging state) */
        [${DOM_MARK}].dragging {
          transition: none !important;
          cursor: grabbing !important;
          user-select: none !important;
          opacity: 0.9 !important;
        }

        [${DOM_MARK}].dragging * {
          pointer-events: none !important;
          cursor: grabbing !important;
        }

        /* Trạng thái thu gọn (Collapsed state) */
        [${DOM_MARK}].collapsed {
          width: 55px;
          overflow: hidden;
        }

        /* Thu gọn + KHÔNG hover => trong suốt toàn bộ */
        /* Collapsed + NOT hovering => fully transparent */
        [${DOM_MARK}].collapsed:not(.hovering) {
          opacity: ${COLLAPSED_OPACITY};
        }

        [${DOM_MARK}].collapsed .questions-list {
          opacity: 0.8;
          visibility: visible;
          transition: opacity 0.45s ease, visibility 0.45s ease;
        }

        /* Hiển thị đầy đủ khi mở rộng hoặc hover */
        /* Full display when expanded or hovering */
        [${DOM_MARK}]:not(.collapsed) .questions-list,
        [${DOM_MARK}].hovering:not(.dragging) .questions-list {
          opacity: 1;
          visibility: visible;
          transition: opacity 0.45s ease, visibility 0.45s ease;
        }

        /* Trạng thái hover (Hover state) */
        [${DOM_MARK}].hovering:not(.dragging) {
          width: auto;
          max-width: 300px;
          overflow: visible;
          transition: all 0.65s cubic-bezier(0.4, 0, 0.2, 1);
          opacity: 1; /* Hover vào để xem nội dung rõ ràng (Hover for clear content view) */
        }

        /* Header của sidebar (Sidebar header) */
        [${DOM_MARK}] .header {
          display: flex;
          align-items: center;
          justify-content: space-between;
          margin-bottom: 8px;
          color: #e5e7eb;
          font-weight: 600;
          font-size: 14px;
          white-space: nowrap;
          gap: 8px;
          cursor: default; /* Không còn kéo bằng header (No longer drag by header) */
          user-select: none;
        }

        [${DOM_MARK}] .header:hover {
          background: rgba(255, 255, 255, 0.05);
          margin: -4px -4px 4px -4px;
          padding: 4px;
          border-radius: 4px;
        }

        /* Tiêu đề sidebar (Sidebar title) */
        [${DOM_MARK}] .title {
          opacity: 1;
          transition: opacity 0.45s ease;
          pointer-events: none;
        }

        [${DOM_MARK}].collapsed:not(.hovering) .title {
          opacity: 0;
          width: 0;
          overflow: hidden;
        }

        /* Nút toggle/kéo (Toggle/drag button) */
        [${DOM_MARK}] .toggle-btn {
          cursor: grab; /* Chỉ kéo bằng nút ✮⋆˙ (Only drag using ✮⋆˙ button) */
          opacity: 0.8;
          transition: all 0.45s ease;
          font-size: 20px;
          padding: 2px;
          filter: grayscale(0);
          flex-shrink: 0;
          margin-left: auto;
        }

        [${DOM_MARK}] .toggle-btn:hover {
          opacity: 1;
          transform: scale(1.1);
        }

        [${DOM_MARK}].collapsed .toggle-btn {
          opacity: 1;
          filter: grayscale(0);
        }

        /* Danh sách câu hỏi (Questions list) */
        [${DOM_MARK}] .questions-list {
          max-height: 60vh;
          overflow-y: auto;
          margin: 0;
          padding: 0;
          list-style: none;
        }

        /* Thanh cuộn tùy chỉnh (Custom scrollbar) */
        [${DOM_MARK}] .questions-list::-webkit-scrollbar {
          width: 4px;
        }

        [${DOM_MARK}] .questions-list::-webkit-scrollbar-track {
          background: transparent;
        }

        [${DOM_MARK}] .questions-list::-webkit-scrollbar-thumb {
          background: rgba(255, 255, 255, 0.2);
          border-radius: 2px;
        }

        [${DOM_MARK}] .questions-list::-webkit-scrollbar-thumb:hover {
          background: rgba(255, 255, 255, 0.3);
        }

        /* Mục câu hỏi (Question item) */
        [${DOM_MARK}] .question-item {
          padding: 6px 8px;
          margin: 2px 0;
          color: #9ca3af;
          font-size: 13px;
          cursor: pointer;
          border-radius: 4px;
          transition: all 0.32s ease;
          white-space: nowrap;
          overflow: hidden;
          text-overflow: ellipsis;
          display: flex;
          align-items: flex-start;
          gap: 8px;
        }

        [${DOM_MARK}].collapsed:not(.hovering) .question-item {
          padding: 6px 4px;
        }

        [${DOM_MARK}] .question-item:hover {
          background: rgba(255, 255, 255, 0.05);
          color: #e5e7eb;
        }

        /* Câu hỏi đang active (Active question) */
        [${DOM_MARK}] .question-item.active {
          background: rgba(16, 185, 129, 0.2);
          color: #10b981;
          font-weight: 500;
        }

        /* Số thứ tự câu hỏi (Question number) */
        [${DOM_MARK}] .question-number {
          flex-shrink: 0;
          font-weight: 600;
          color: #6b7280;
          min-width: 20px;
          text-align: left;
        }

        [${DOM_MARK}] .question-item.active .question-number {
          color: #10b981;
        }

        /* Text câu hỏi (Question text) */
        [${DOM_MARK}] .question-text {
          flex: 1;
          overflow: hidden;
          text-overflow: ellipsis;
          transition: opacity 0.45s ease;
        }

        [${DOM_MARK}].collapsed:not(.hovering) .question-text {
          opacity: 0;
          width: 0;
        }
      </style>

      <div class="header">
        <span class="title">📋 Questions</span>
        <span class="toggle-btn">✮⋆˙</span>
      </div>
      <ol class="questions-list"></ol>
    `;

    return sidebar;
  }

  // ====== CHỨC NĂNG KÉO THẢ (DRAG FUNCTIONALITY) ======
  // Chỉ kéo khi giữ nút ✮⋆˙ và chỉ thay đổi tọa độ Y (neo bên phải)
  // Only drag when holding ✮⋆˙ button and only change Y coordinate (anchored to right)
  function setupDragBehavior(sidebar) {
    const toggleBtn = sidebar.querySelector('.toggle-btn');
    let isPointerDown = false;
    let isDragging = false;
    let startY = 0;
    let startTop = 0;
    let ignoreNextClick = false;

    // Luôn neo bên phải (Always anchor to the right)
    sidebar.style.right = RIGHT_OFFSET_PX + 'px';
    sidebar.style.left = '';

    // Load vị trí top đã lưu (nếu có) / Load saved top position (if exists)
    const savedTop = localStorage.getItem('chatgpt-sidebar-top');
    if (savedTop) {
      sidebar.style.top = savedTop;
    }

    // Xóa giá trị right cũ không dùng nữa (Remove unused old right value)
    try { localStorage.removeItem('chatgpt-sidebar-right'); } catch {}

    // Bắt đầu kéo (Start dragging)
    function onPointerDown(e) {
      // Chỉ cho phép kéo khi bấm lên nút ✮⋆˙
      // Only allow dragging when clicking on ✮⋆˙ button
      if (e.target !== toggleBtn) return;

      isPointerDown = true;
      isDragging = false;
      startY = (e.touches ? e.touches[0].clientY : e.clientY);
      startTop = sidebar.getBoundingClientRect().top;

      // Lắng nghe di chuyển/thả trên document
      // Listen for move/release on document
      document.addEventListener('mousemove', onPointerMove);
      document.addEventListener('mouseup', onPointerUp);
      document.addEventListener('touchmove', onPointerMove, { passive: false });
      document.addEventListener('touchend', onPointerUp);
    }

    // Xử lý di chuyển chuột/touch (Handle mouse/touch move)
    function onPointerMove(e) {
      if (!isPointerDown) return;

      const clientY = (e.touches ? e.touches[0].clientY : e.clientY);
      const deltaY = clientY - startY;

      // Kiểm tra ngưỡng để bắt đầu kéo (Check threshold to start dragging)
      if (!isDragging && Math.abs(deltaY) > DRAG_THRESHOLD_PX) {
        isDragging = true;
        sidebar.classList.add('dragging');
        sidebar.classList.remove('hovering');
      }

      if (!isDragging) return;

      // Tính top mới, chỉ theo trục Y (Calculate new top, only Y axis)
      const rect = sidebar.getBoundingClientRect();
      const minTop = TOP_MIN_MARGIN;
      const maxTop = window.innerHeight - rect.height - BOTTOM_MIN_MARGIN;

      let newTop = startTop + deltaY;
      newTop = Math.max(minTop, Math.min(newTop, maxTop)); // Giới hạn trong vùng cho phép (Limit within allowed area)

      // Áp dụng vị trí (Apply position)
      sidebar.style.top = newTop + 'px';
      sidebar.style.right = RIGHT_OFFSET_PX + 'px';
      sidebar.style.left = '';

      e.preventDefault();
      e.stopPropagation();
    }

    // Kết thúc kéo (End dragging)
    function onPointerUp() {
      if (!isPointerDown) return;

      if (isDragging) {
        // Lưu tọa độ Y (top) và vẫn neo phải
        // Save Y coordinate (top) and keep anchored to right
        const rect = sidebar.getBoundingClientRect();
        localStorage.setItem('chatgpt-sidebar-top', rect.top + 'px');
        sidebar.style.right = RIGHT_OFFSET_PX + 'px';

        // Tránh click toggle ngay sau khi kéo
        // Avoid toggle click right after dragging
        ignoreNextClick = true;
        setTimeout(() => { ignoreNextClick = false; }, 0);
      }

      isPointerDown = false;
      isDragging = false;
      sidebar.classList.remove('dragging');

      // Gỡ bỏ các event listeners (Remove event listeners)
      document.removeEventListener('mousemove', onPointerMove);
      document.removeEventListener('mouseup', onPointerUp);
      document.removeEventListener('touchmove', onPointerMove);
      document.removeEventListener('touchend', onPointerUp);
    }

    // Click nút ✮⋆˙: toggle thu gọn/mở rộng (trừ khi vừa kéo)
    // Click ✮⋆˙ button: toggle collapse/expand (except after dragging)
    function onToggleClick(e) {
      if (ignoreNextClick) {
        e.preventDefault();
        e.stopPropagation();
        return;
      }
      e.stopPropagation();
      toggleSidebar(sidebar);
    }

    // Gắn events (Attach events)
    toggleBtn.addEventListener('mousedown', onPointerDown);
    toggleBtn.addEventListener('touchstart', onPointerDown, { passive: true });
    toggleBtn.addEventListener('click', onToggleClick);

    // Hàm dọn dẹp (Cleanup function)
    sidebar._cleanupDrag = () => {
      toggleBtn.removeEventListener('mousedown', onPointerDown);
      toggleBtn.removeEventListener('touchstart', onPointerDown);
      toggleBtn.removeEventListener('click', onToggleClick);
      document.removeEventListener('mousemove', onPointerMove);
      document.removeEventListener('mouseup', onPointerUp);
      document.removeEventListener('touchmove', onPointerMove);
      document.removeEventListener('touchend', onPointerUp);
    };
  }

  // Cuộn danh sách sidebar xuống cuối (Scroll sidebar list to bottom)
  function scrollSidebarToBottom(sidebar) {
    const list = sidebar.querySelector('.questions-list');
    if (list) {
      list.scrollTop = list.scrollHeight;
      // Đảm bảo cuộn sau khi render (Ensure scroll after render)
      setTimeout(() => {
        list.scrollTop = list.scrollHeight;
      }, 100);
    }
  }

  // ====== QUẢN LÝ CÂU HỎI ACTIVE (ACTIVE QUESTION MANAGEMENT) ======
  // Set câu hỏi cụ thể làm active
  // Set specific question as active
  // scrollChat: true => cuộn KHUNG CHAT tới câu hỏi đó (scroll CHAT FRAME to that question)
  // scrollChat: false => KHÔNG đụng tới khung chat, chỉ highlight (DON'T touch chat frame, only highlight)
  function setActiveQuestion(sidebar, index, questionEls, opts = {}) {
    const { scrollChat = true } = opts;

    // Cập nhật class active cho các mục trong sidebar
    // Update active class for sidebar items
    sidebar.querySelectorAll('.question-item').forEach((item, i) => {
      item.classList.toggle('active', i === index);
    });

    // Cuộn khung chat nếu được yêu cầu (Scroll chat frame if requested)
    if (scrollChat && questionEls[index]) {
      setTimeout(() => {
        questionEls[index].scrollIntoView({ behavior: 'smooth', block: 'start' });
      }, 100);
    }
  }

  // Cập nhật câu hỏi active dựa trên vị trí scroll (không cuộn chat)
  // Update active question based on scroll position (without scrolling chat)
  function updateActiveQuestion(sidebar, questionEls) {
    const topThreshold = isSharePage ? scrollMarginTop : 0;
    let activeIndex = -1;

    // Tìm câu hỏi đầu tiên xuất hiện trong viewport
    // Find first question appearing in viewport
    for (let i = 0; i < questionEls.length; i++) {
      const rect = questionEls[i].getBoundingClientRect();
      if (rect.top >= topThreshold) {
        activeIndex = i;
        break;
      }
    }

    // Cập nhật class active (Update active class)
    sidebar.querySelectorAll('.question-item').forEach((item, index) => {
      item.classList.toggle('active', index === activeIndex);
    });
  }

  // ====== CẬP NHẬT DANH SÁCH CÂU HỎI (UPDATE QUESTIONS LIST) ======
  // Cập nhật danh sách câu hỏi (đảm bảo dính cuối nếu cần, và KHÔNG cuộn khung chat khi khởi tạo)
  // Update questions list (ensure stick to bottom if needed, and DON'T scroll chat frame on init)
  function updateQuestionsList(sidebar, scrollToLast = false) {
    const list = sidebar.querySelector('.questions-list');
    if (!list) return;

    const questions = getQuestions();
    const questionEls = queryQuestionEls();

    // Lưu vị trí cuộn trước khi rebuild (Save scroll position before rebuild)
    const prevScrollTop = list.scrollTop;
    const prevAtBottom = (list.scrollTop + list.clientHeight >= list.scrollHeight - 5);

    // Rebuild danh sách (Rebuild list)
    list.innerHTML = questions.map((q, index) => `
      <li class="question-item" data-index="${index}">
        <span class="question-number">${index + 1}.</span>
        <span class="question-text">${q}</span>
      </li>
    `).join('');

    // Kiểm tra có nên dính cuối không (Check if should stick to bottom)
    const forceWindow = Date.now() < (sidebar._forceBottomUntil || 0);
    const shouldStick = scrollToLast || forceWindow || sidebar._stickToBottom || prevAtBottom;

    if (questions.length > 0 && shouldStick) {
      // Quan trọng: KHÔNG cuộn khung chat ở nhánh này
      // Important: DON'T scroll chat frame in this branch
      setActiveQuestion(sidebar, questions.length - 1, questionEls, { scrollChat: false });
      requestAnimationFrame(() => scrollSidebarToBottom(sidebar));
    } else {
      updateActiveQuestion(sidebar, questionEls);
      // Khôi phục vị trí cuộn nếu không dính cuối
      // Restore scroll position if not sticking to bottom
      requestAnimationFrame(() => { list.scrollTop = prevScrollTop; });
    }
  }

  // Xử lý click vào câu hỏi (click mới cuộn khung chat)
  // Handle click on question (only click scrolls chat frame)
  function handleQuestionClick(e, sidebar) {
    const item = e.target.closest('.question-item');
    if (!item) return;

    const index = parseInt(item.dataset.index);
    const questionEls = queryQuestionEls();

    // Click => cuộn khung chat tới câu hỏi đó + set active
    // Click => scroll chat frame to that question + set active
    setActiveQuestion(sidebar, index, questionEls, { scrollChat: true });
  }

  // Toggle thu gọn/mở rộng sidebar (Toggle collapse/expand sidebar)
  function toggleSidebar(sidebar) {
    const isCollapsed = sidebar.classList.contains('collapsed');
    sidebar.classList.toggle('collapsed');
    sidebar.classList.remove('hovering');
    sidebar._isManuallyToggled = true;
    sidebar._isOpen = isCollapsed;
  }

  // ====== CHỨC NĂNG HOVER (HOVER FUNCTIONALITY) ======
  function setupHoverBehavior(sidebar) {
    let hoverTimeout;

    // Khi di chuột vào (On mouse enter)
    sidebar.addEventListener('mouseenter', () => {
      if (!sidebar.classList.contains('dragging')) {
        clearTimeout(hoverTimeout);
        sidebar.classList.add('hovering');
      }
    });

    // Khi di chuột ra (On mouse leave)
    sidebar.addEventListener('mouseleave', () => {
      if (!sidebar.classList.contains('dragging')) {
        clearTimeout(hoverTimeout);
        // Delay trước khi tắt hover (Delay before removing hover)
        hoverTimeout = setTimeout(() => {
          sidebar.classList.remove('hovering');
        }, 234);
      }
    });
  }

  // ====== KHỞI TẠO CHÍNH (MAIN INITIALIZATION) ======
  function init(isFirstLoad = false) {
    // Xóa sidebar cũ nếu có (Remove old sidebar if exists)
    const existing = document.querySelector(`[${DOM_MARK}]`);
    if (existing) {
      existing._cleanup?.();
      existing._cleanupDrag?.();
      existing.remove();
    }

    // Kiểm tra có câu hỏi không (Check if there are questions)
    const questionEls = queryQuestionEls();
    if (questionEls.length === 0) return;

    // Tạo và thêm sidebar mới (Create and add new sidebar)
    const sidebar = createSidebar();
    document.body.appendChild(sidebar);

    // Khởi tạo trạng thái (Initialize state)
    sidebar._isManuallyToggled = false;
    sidebar._isOpen = false;
    sidebar.classList.add('collapsed');

    const list = sidebar.querySelector('.questions-list');

    // Trạng thái "dính cuối" (Stick to bottom state)
    sidebar._stickToBottom = true; // Lần đầu luôn coi như dính cuối (First time always stick to bottom)
    sidebar._forceBottomUntil = Date.now() + FORCE_STICK_BOTTOM_MS;

    // Theo dõi scroll của sidebar để biết khi nào người dùng kéo lên
    // Track sidebar scroll to know when user scrolls up
    list.addEventListener('scroll', () => {
      const atBottom = (list.scrollTop + list.clientHeight >= list.scrollHeight - 5);
      sidebar._stickToBottom = atBottom;
    });

    // Click câu hỏi (Question click)
    list.addEventListener('click', (e) => handleQuestionClick(e, sidebar));

    // Setup các behavior (Setup behaviors)
    setupHoverBehavior(sidebar);
    setupDragBehavior(sidebar);

    // Lần đầu: build list và chỉ cuộn sidebar về cuối (không đụng tới chat)
    // First time: build list and only scroll sidebar to bottom (don't touch chat)
    updateQuestionsList(sidebar, isFirstLoad);

    // Theo dõi scroll của khung chat (Track chat frame scroll)
    const scrollContainer = queryChatContainer()?.parentElement;
    if (scrollContainer) {
      let scrollTimeout;
      scrollContainer.addEventListener('scroll', () => {
        clearTimeout(scrollTimeout);
        // Debounce để tránh cập nhật quá nhiều (Debounce to avoid too many updates)
        scrollTimeout = setTimeout(() => {
          updateActiveQuestion(sidebar, queryQuestionEls());
        }, 50);
      });
    }

    // Quan sát thay đổi DOM để cập nhật danh sách
    // Observe DOM changes to update list
    const observer = new MutationObserver(() => {
      updateQuestionsList(sidebar);
    });

    const chatContainer = queryChatContainer();
    if (chatContainer) {
      observer.observe(chatContainer, { childList: true });
    }

    // Hàm dọn dẹp (Cleanup function)
    sidebar._cleanup = () => {
      observer.disconnect();
    };
  }

  // ====== GIÁM SÁT THAY ĐỔI (MONITOR FOR CHANGES) ======
  let loaded = false;
  let conversationId = null;
  let isInitialLoad = true;

  // Kiểm tra định kỳ để phát hiện thay đổi conversation
  // Periodically check to detect conversation changes
  setInterval(() => {
    const latestConversationId = getConversationIdByUrl();
    const hasQuestions = queryQuestionEls().length > 0;

    // Nếu conversation thay đổi hoặc không có câu hỏi
    // If conversation changed or no questions
    if (conversationId !== latestConversationId || !hasQuestions) {
      conversationId = latestConversationId;

      // Xóa sidebar cũ (Remove old sidebar)
      const existing = document.querySelector(`[${DOM_MARK}]`);
      if (existing) {
        existing._cleanup?.();
        existing._cleanupDrag?.();
        existing.remove();
      }
      loaded = false;

      // Reset trạng thái initial load khi đổi conversation
      // Reset initial load state when conversation changes
      if (conversationId !== latestConversationId) {
        isInitialLoad = true;
      }
    }

    // Khởi tạo sidebar mới nếu có câu hỏi
    // Initialize new sidebar if there are questions
    if (!loaded && hasQuestions) {
      init(isInitialLoad);
      loaded = true;
      isInitialLoad = false;
    }
  }, 600); // Kiểm tra mỗi 600ms (Check every 600ms)
})();