Greasy Fork

Greasy Fork is available in English.

webAI聊天问题列表导航

通过点击按钮显示用户问题列表,支持导航到特定问题、分页功能、正序/倒序切换,智能脉冲式加载历史记录突破懒加载,自动适配暗黑模式,按钮可拖动并保存位置,悬浮窗智能展开方向

当前为 2025-11-21 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         webAI聊天问题列表导航
// @namespace    http://tampermonkey.net/
// @version      3.6.1
// @description  通过点击按钮显示用户问题列表,支持导航到特定问题、分页功能、正序/倒序切换,智能脉冲式加载历史记录突破懒加载,自动适配暗黑模式,按钮可拖动并保存位置,悬浮窗智能展开方向
// @author       yutao
// @match        https://grok.com/*
// @match        https://github.com/copilot/*
// @match        https://yuanbao.tencent.com/chat/*
// @match        https://chat.qwen.ai/c/*
// @match        https://copilot.microsoft.com/chats/*
// @match        https://chatgpt.com/c/*
// @match        https://chat.deepseek.com/a/chat/*
// @match        https://www.tongyi.com/*
// @match        https://www.qianwen.com/*
// @match        https://www.doubao.com/*
// @match        https://www.chatglm.cn/*
// @match        https://www.kimi.com/chat/*
// @match        https://copilot.wps.cn/*

// @grant        none
// MIT License
//
// Copyright (c) [2025] [yutao]
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.@license
// ==/UserScript==

(function () {
  "use strict";

  // 配置对象,定义不同网站的聊天消息选择器和条件
  const config = {
    "chat.qwen.ai": {
      messageSelector: "div.rounded-3xl.bg-gray-50.dark\\:bg-gray-850",
      textSelector: "p",
      userCondition: (element) => true,
      scrollContainerSelector:
        'div.overflow-y-auto, div[class*="chat-content"]',
    },
    "tongyi.com": {
      messageSelector: 'div[class*="questionItem"]',
      textSelector: 'div[class*="contentBox"] div[class*="bubble"]',
      userCondition: (element) => true,
      scrollContainerSelector: 'div[class*="contentWrapper"], main, div[class*="chat-content"], div[class*="chatContent"]',
    },
    "qianwen.com": {
      messageSelector: 'div[class*="questionItem"]',
      textSelector: 'div[class*="contentBox"] div[class*="bubble"]',
      userCondition: (element) => true,
      scrollContainerSelector: 'div[class*="contentWrapper"], main, div[class*="chat-content"], div[class*="chatContent"]',
    },

    "yuanbao.tencent.com": {
      messageSelector: "div.agent-chat__bubble__content",
      textSelector: "div.hyc-content-text",
      userCondition: (element) => true,
      scrollContainerSelector: ".agent-chat__bubble-wrap",
    },

    "doubao.com": {
      messageSelector: 'div[data-testid="send_message"]',
      textSelector: 'div[data-testid="message_text_content"]',
      userCondition: (element) => true,
      scrollContainerSelector:
        'div[class*="scrollable-"][class*="show-scrollbar-"]',
    },

    "copilot.wps.cn": {
      messageSelector: 'li.item--user, div[class*="item--user"], li[class*="item--user"], .item.item--user',
      textSelector: '.item__value span, div[class*="item__value"] span, .item__value, [class*="item__value"]',
      userCondition: (element) => true,
      scrollContainerSelector: '.chat, .p__main, div[class*="scrollbar"], div[class*="chat-list"], div[class*="scroll"], .scroll-container',
    },
    "www.kimi.com": {
      messageSelector: 'div.segment-user, div[class*="segment-user"]',
      textSelector: '.user-content, div[class*="user-content"]',
      userCondition: (element) => true,
      scrollContainerSelector:
        'div[class*="scrollbar"], div[class*="chat-history"]',
    },

    "chatglm.cn": {
      messageSelector: 'div.conversation.question, div[id*="row-question"]',
      textSelector: '.question-txt span, div[id*="row-question-p"] span',
      userCondition: (element) => true,
      scrollContainerSelector:
        'div[class*="chat-history"], div[class*="scrollable"]',
    },


    "chat.deepseek.com": {
      messageSelector: "div.fbb737a4",
      textSelector: null,
      userCondition: (element) => true,
      scrollContainerSelector: ".scroll-container",
    },

    "grok.com": {
      messageSelector: 'div[class*="message"], div[data-testid*="message"], div[class*="chat-message"]',
      textSelector: 'div[class*="content"], span[class*="text"], p',
      userCondition: (element) => {
        // Grok 用户消息通常在右侧或有特定的类名
        const classes = element.className.toLowerCase();
        const hasUserClass = classes.includes('user') || classes.includes('human') || classes.includes('sender');
        
        // 检查是否在右侧(用户消息通常右对齐)
        const style = window.getComputedStyle(element);
        const isRightAligned = style.justifyContent === 'flex-end' || 
                               style.alignSelf === 'flex-end' ||
                               style.marginLeft === 'auto';
        
        // 检查背景色(用户消息通常有不同的背景色)
        const bgColor = style.backgroundColor;
        const isUserBg = bgColor && bgColor !== 'rgba(0, 0, 0, 0)' && bgColor !== 'transparent';
        
        return hasUserClass || isRightAligned || (isUserBg && !classes.includes('assistant') && !classes.includes('bot'));
      },
      scrollContainerSelector: 'main, div[class*="scroll"], div[class*="chat-container"], div[class*="messages"]',
    },
    "github.com": {
      messageSelector:
        "div.UserMessage-module__container--cAvvK.ChatMessage-module__userMessage--xvIFp",
      textSelector: null,
      userCondition: (element) =>
        element.classList.contains("ChatMessage-module__userMessage--xvIFp"),
      scrollContainerSelector: ".react-scroll-to-bottom--css-xgtui-79elbk",
    },

    "copilot.microsoft.com": {
      messageSelector: "div.self-end.rounded-2xl",
      textSelector: null,
      userCondition: (element) => element.classList.contains("self-end"),
      scrollContainerSelector: ".overflow-y-auto.flex-1",
    },
    "chatgpt.com": {
      messageSelector: "div.rounded-3xl.bg-token-message-surface",
      textSelector: "div.whitespace-pre-wrap",
      userCondition: (element) => true,
      scrollContainerSelector: "main div.overflow-y-auto",
    },
  };

  const genericConfig = {
    // 消息选择器:匹配常见的消息元素模式
    messageSelector:
      'div[class*="message"], div[class*="chat"], div[class*="user"], div[class*="question"], div[class*="questionItem"]',

    // 文本选择器:匹配常见的文本容器
    textSelector:
      'div[class*="text"], div[class*="content"], p, span[class*="content"], div[class*="contentBox"]',

    // 用户消息条件:使用多种通用的方法识别用户消息
    userCondition: (element) => {
      // 检查常见的用户消息类名
      if (
        element.classList.toString().includes("user") ||
        element.classList.toString().includes("question") ||
        element.classList.toString().includes("self") ||
        element.classList.toString().includes("right") ||
        element.classList.toString().includes("message")
      )
        return true;

      // 检查常见的用户角色属性
      if (
        element.getAttribute("data-role") === "user" ||
        element.getAttribute("data-author") === "user" ||
        element.getAttribute("data-message-author-role") === "user"
      )
        return true;

      // 检查布局特征 (右对齐通常表示用户消息)
      const style = window.getComputedStyle(element);
      if (
        style.justifyContent === "flex-end" ||
        style.textAlign === "right" ||
        style.alignSelf === "flex-end"
      )
        return true;

      // 检查文本内容特征:如果没有包含AI常用的前缀标识
      const text = element.textContent.trim().toLowerCase();
      if (
        text &&
        text.length > 0 &&
        !text.startsWith("ai:") &&
        !text.startsWith("assistant:") &&
        !text.startsWith("bot:")
      )
        return true;

      return false;
    },

    // 滚动容器选择器:匹配常见的滚动容器
    scrollContainerSelector:
      'div[class*="overflow"], div[class*="scroll"], div[class*="chat-container"], div[class*="message-container"], #messages-container, main',
  };

  // 获取当前域名并选择配置
  const hostname = window.location.hostname;

  // 获取当前网站的配置,如果没有特定配置则使用通用配置
  const currentConfig = config[hostname] || genericConfig;

  // 暗黑模式检测和主题管理
  const themeManager = {
    isDark: false,
    
    // 检测暗黑模式
    detectDarkMode() {
      // 1. 检查系统偏好
      const systemDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
      
      // 2. 检查网站是否使用暗黑模式
      const htmlDark = document.documentElement.classList.contains('dark') || 
                       document.documentElement.getAttribute('data-theme') === 'dark';
      const bodyDark = document.body.classList.contains('dark') || 
                       document.body.getAttribute('data-theme') === 'dark';
      
      // 3. 检查背景色
      const bodyBg = window.getComputedStyle(document.body).backgroundColor;
      const bgDark = this.isColorDark(bodyBg);
      
      this.isDark = htmlDark || bodyDark || bgDark || systemDark;
      return this.isDark;
    },
    
    // 判断颜色是否为暗色
    isColorDark(color) {
      const rgb = color.match(/\d+/g);
      if (!rgb || rgb.length < 3) return false;
      const brightness = (parseInt(rgb[0]) * 299 + parseInt(rgb[1]) * 587 + parseInt(rgb[2]) * 114) / 1000;
      return brightness < 128;
    },
    
    // 获取主题颜色
    getColors() {
      if (this.isDark) {
        return {
          // 暗黑模式
          buttonBg: "linear-gradient(135deg, #1e40af, #0ea5e9)",
          buttonColor: "#e5e7eb",
          windowBg: "#1f2937",
          windowBorder: "#374151",
          windowShadow: "0 4px 12px rgba(0,0,0,0.5)",
          textPrimary: "#f3f4f6",
          textSecondary: "#9ca3af",
          itemHoverBg: "#374151",
          itemBorder: "#4b5563",
          buttonPrimaryBg: "#10b981",
          buttonPrimaryHover: "#059669",
          buttonSecondaryBg: "#3b82f6",
          buttonSecondaryHover: "#2563eb",
          statusBg: "#374151",
          statusBorder: "#4b5563",
          paginationBg: "#374151",
          paginationActiveBg: "#3b82f6",
          paginationColor: "#e5e7eb",
        };
      } else {
        return {
          // 亮色模式
          buttonBg: "linear-gradient(135deg, #007BFF, #00C4FF)",
          buttonColor: "#fff",
          windowBg: "#ffffff",
          windowBorder: "#e0e0e0",
          windowShadow: "0 4px 12px rgba(0,0,0,0.15)",
          textPrimary: "#333",
          textSecondary: "#666",
          itemHoverBg: "#f5f5f5",
          itemBorder: "#f0f0f0",
          buttonPrimaryBg: "#28a745",
          buttonPrimaryHover: "#218838",
          buttonSecondaryBg: "#007BFF",
          buttonSecondaryHover: "#0069d9",
          statusBg: "#f8f9fa",
          statusBorder: "#e0e0e0",
          paginationBg: "#f0f0f0",
          paginationActiveBg: "#007BFF",
          paginationColor: "#333",
        };
      }
    }
  };

  // 初始化主题
  themeManager.detectDarkMode();
  const colors = themeManager.getColors();

  // 位置管理器 - 保存和恢复按钮位置
  const positionManager = {
    storageKey: 'questionListButton_position',
    
    // 获取保存的位置
    getSavedPosition() {
      try {
        const saved = localStorage.getItem(this.storageKey);
        return saved ? JSON.parse(saved) : null;
      } catch (e) {
        return null;
      }
    },
    
    // 保存位置
    savePosition(bottom, right) {
      try {
        localStorage.setItem(this.storageKey, JSON.stringify({ bottom, right }));
      } catch (e) {
        console.warn('无法保存按钮位置');
      }
    },
    
    // 获取默认位置
    getDefaultPosition() {
      return { bottom: 20, right: 20 };
    }
  };

  // 创建美化后的浮动按钮
  const button = document.createElement("button");
  button.textContent = "问题列表";
  button.style.position = "fixed";
  button.style.zIndex = "1000";
  button.style.padding = "10px 15px";
  button.style.background = colors.buttonBg;
  button.style.color = colors.buttonColor;
  button.style.border = "none";
  button.style.borderRadius = "8px";
  button.style.boxShadow = "0 2px 4px rgba(0,0,0,0.2)";
  button.style.cursor = "move";
  button.style.fontFamily = "Arial, sans-serif";
  button.style.fontSize = "14px";
  button.style.transition = "transform 0.2s, box-shadow 0.2s";
  button.style.userSelect = "none";
  
  // 恢复保存的位置或使用默认位置
  const savedPos = positionManager.getSavedPosition() || positionManager.getDefaultPosition();
  button.style.bottom = savedPos.bottom + "px";
  button.style.right = savedPos.right + "px";
  
  // 拖动功能
  let isDragging = false;
  let dragStartX = 0;
  let dragStartY = 0;
  let buttonStartBottom = 0;
  let buttonStartRight = 0;
  
  button.addEventListener("mousedown", (e) => {
    // 只在左键点击时开始拖动
    if (e.button !== 0) return;
    
    isDragging = true;
    dragStartX = e.clientX;
    dragStartY = e.clientY;
    buttonStartBottom = parseInt(button.style.bottom);
    buttonStartRight = parseInt(button.style.right);
    
    button.style.cursor = "grabbing";
    e.preventDefault();
  });
  
  document.addEventListener("mousemove", (e) => {
    if (!isDragging) return;
    
    const deltaX = dragStartX - e.clientX;
    const deltaY = dragStartY - e.clientY; // 修正:向下拖动时 deltaY 应该为负
    
    let newBottom = buttonStartBottom + deltaY;
    let newRight = buttonStartRight + deltaX;
    
    // 限制在窗口范围内
    const maxBottom = window.innerHeight - button.offsetHeight - 10;
    const maxRight = window.innerWidth - button.offsetWidth - 10;
    
    newBottom = Math.max(10, Math.min(newBottom, maxBottom));
    newRight = Math.max(10, Math.min(newRight, maxRight));
    
    button.style.bottom = newBottom + "px";
    button.style.right = newRight + "px";
    
    e.preventDefault();
  });
  
  document.addEventListener("mouseup", (e) => {
    if (isDragging) {
      isDragging = false;
      button.style.cursor = "move";
      
      // 保存位置
      const bottom = parseInt(button.style.bottom);
      const right = parseInt(button.style.right);
      positionManager.savePosition(bottom, right);
      
      // 更新悬浮窗位置
      updateFloatWindowPosition();
      
      // 如果移动距离很小,视为点击
      const moveDistance = Math.sqrt(
        Math.pow(e.clientX - dragStartX, 2) + 
        Math.pow(e.clientY - dragStartY, 2)
      );
      
      if (moveDistance < 5) {
        // 触发点击事件
        setTimeout(() => {
          toggleFloatWindow();
        }, 0);
      }
    }
  });
  
  button.addEventListener("mouseover", () => {
    if (!isDragging) {
      button.style.transform = "scale(1.05)";
      button.style.boxShadow = "0 4px 8px rgba(0,0,0,0.3)";
    }
  });
  button.addEventListener("mouseout", () => {
    if (!isDragging) {
      button.style.transform = "scale(1)";
      button.style.boxShadow = "0 2px 4px rgba(0,0,0,0.2)";
    }
  });
  
  document.body.appendChild(button);

  // 创建美化后的悬浮窗
  const floatWindow = document.createElement("div");
  floatWindow.style.position = "fixed";
  floatWindow.style.width = "320px";
  floatWindow.style.maxHeight = "420px";
  floatWindow.style.background = colors.windowBg;
  floatWindow.style.border = `1px solid ${colors.windowBorder}`;
  floatWindow.style.borderRadius = "10px";
  floatWindow.style.boxShadow = colors.windowShadow;
  floatWindow.style.padding = "15px";
  floatWindow.style.overflowY = "auto";
  floatWindow.style.display = "none";
  floatWindow.style.zIndex = "1000";
  floatWindow.style.fontFamily = "Arial, sans-serif";
  floatWindow.style.transition = "opacity 0.2s";
  
  // 更新悬浮窗位置的函数 - 智能选择展开方向
  function updateFloatWindowPosition() {
    const buttonBottom = parseInt(button.style.bottom);
    const buttonRight = parseInt(button.style.right);
    const buttonWidth = button.offsetWidth;
    const buttonHeight = button.offsetHeight;
    
    const windowWidth = window.innerWidth;
    const windowHeight = window.innerHeight;
    
    const floatWindowWidth = 320;
    const floatWindowHeight = 420; // maxHeight
    const gap = 10; // 间距
    
    // 计算按钮在屏幕上的实际位置
    const buttonLeft = windowWidth - buttonRight - buttonWidth;
    const buttonTop = windowHeight - buttonBottom - buttonHeight;
    
    // 重置之前的定位属性
    floatWindow.style.top = 'auto';
    floatWindow.style.bottom = 'auto';
    floatWindow.style.left = 'auto';
    floatWindow.style.right = 'auto';
    
    // 1. 判断垂直方向:上方还是下方展开
    const spaceAbove = buttonTop; // 按钮上方的空间
    const spaceBelow = windowHeight - buttonTop - buttonHeight; // 按钮下方的空间
    
    if (spaceAbove >= floatWindowHeight || spaceAbove >= spaceBelow) {
      // 上方空间足够,在按钮上方展开
      floatWindow.style.bottom = (buttonBottom + buttonHeight + gap) + "px";
    } else {
      // 上方空间不足,在按钮下方展开
      floatWindow.style.top = (buttonTop + buttonHeight + gap) + "px";
    }
    
    // 2. 判断水平方向:左侧还是右侧对齐
    const spaceOnRight = buttonLeft + buttonWidth;
    const spaceOnLeft = windowWidth - buttonLeft;
    
    if (spaceOnRight >= floatWindowWidth) {
      // 右对齐(悬浮窗在按钮左侧或与按钮右边缘对齐)
      floatWindow.style.right = buttonRight + "px";
    } else if (spaceOnLeft >= floatWindowWidth) {
      // 左对齐(悬浮窗在按钮右侧或与按钮左边缘对齐)
      floatWindow.style.left = buttonLeft + "px";
    } else {
      // 空间不足,居中显示
      const centerLeft = Math.max(gap, (windowWidth - floatWindowWidth) / 2);
      floatWindow.style.left = centerLeft + "px";
    }
  }
  
  // 初始化悬浮窗位置
  updateFloatWindowPosition();
  
  document.body.appendChild(floatWindow);

  // 分页相关变量
  let questions = [];
  const pageSize = 10;
  let currentPage = 1;
  let isReversed = false;
  let isLoading = false; // 加载状态标志
  let autoLoadCompleted = false; // 标记自动加载是否已完成

  // 创建顶部按钮容器
  const topButtonContainer = document.createElement("div");
  topButtonContainer.style.display = "flex";
  topButtonContainer.style.justifyContent = "space-between";
  topButtonContainer.style.marginBottom = "15px";

  // 创建加载历史按钮
  const loadButton = document.createElement("button");
  loadButton.textContent = "加载历史";
  loadButton.style.padding = "5px 10px";
  loadButton.style.background = colors.buttonPrimaryBg;
  loadButton.style.color = "#fff";
  loadButton.style.border = "none";
  loadButton.style.borderRadius = "4px";
  loadButton.style.cursor = "pointer";
  loadButton.style.fontSize = "12px";
  loadButton.style.transition = "background 0.2s";
  loadButton.addEventListener("mouseover", () => {
    loadButton.style.background = colors.buttonPrimaryHover;
  });
  loadButton.addEventListener("mouseout", () => {
    loadButton.style.background = colors.buttonPrimaryBg;
  });
  loadButton.addEventListener("click", () => {
    loadHistoryRecords();
  });

  // 创建排序切换按钮
  const sortButton = document.createElement("button");
  sortButton.textContent = "正序";
  sortButton.style.padding = "5px 10px";
  sortButton.style.background = colors.buttonSecondaryBg;
  sortButton.style.color = "#fff";
  sortButton.style.border = "none";
  sortButton.style.borderRadius = "4px";
  sortButton.style.cursor = "pointer";
  sortButton.style.fontSize = "12px";
  sortButton.style.transition = "background 0.2s";
  sortButton.addEventListener("mouseover", () => {
    sortButton.style.background = colors.buttonSecondaryHover;
  });
  sortButton.addEventListener("mouseout", () => {
    sortButton.style.background = colors.buttonSecondaryBg;
  });
  sortButton.addEventListener("click", () => {
    isReversed = !isReversed;
    sortButton.textContent = isReversed ? "倒序" : "正序";
    findAllQuestionsWithDeduplication();
  });

  // 状态显示标签
  const statusLabel = document.createElement("div");
  statusLabel.textContent = "正在加载历史...";
  statusLabel.style.fontSize = "12px";
  statusLabel.style.color = colors.textSecondary;
  statusLabel.style.padding = "5px 0";
  statusLabel.style.display = "none"; // 默认隐藏

  // 将按钮添加到容器中
  topButtonContainer.appendChild(statusLabel);
  topButtonContainer.appendChild(loadButton);
  topButtonContainer.appendChild(sortButton);
  floatWindow.appendChild(topButtonContainer);

  // 创建分页控件
  const paginationContainer = document.createElement("div");
  paginationContainer.style.display = "flex";
  paginationContainer.style.justifyContent = "center";
  paginationContainer.style.marginTop = "10px";
  paginationContainer.style.gap = "5px";

  // 问题列表容器
  const listContainer = document.createElement("ul");
  listContainer.style.listStyle = "none";
  listContainer.style.padding = "0";
  listContainer.style.margin = "0";
  floatWindow.appendChild(listContainer);
  floatWindow.appendChild(paginationContainer);

  // 创建问题计数显示区域
  const questionCountDisplay = document.createElement("div");
  questionCountDisplay.style.fontSize = "12px";
  questionCountDisplay.style.color = colors.textSecondary;
  questionCountDisplay.style.textAlign = "center";
  questionCountDisplay.style.margin = "5px 0 10px 0";
  floatWindow.insertBefore(questionCountDisplay, listContainer);

  // 更新问题计数显示
  function updateQuestionCountDisplay() {
    questionCountDisplay.textContent = `共找到 ${questions.length} 个问题`;
  }

  // 获取文本内容的辅助函数
  function getTextContent(element) {
    return element ? element.textContent.trim() : "";
  }

  // 查找所有用户问题并去重的函数
  function findAllQuestionsWithDeduplication() {
    const chatContainer =
      document.querySelector(".chat-container, #chat, main, article") ||
      document.body;
    const potentialMessages = chatContainer.querySelectorAll(
      currentConfig.messageSelector
    );

    // 调试信息(仅在找不到消息时输出)
    if (potentialMessages.length === 0) {
      console.log('[问题列表导航] 调试信息:', {
        网站: hostname,
        消息选择器: currentConfig.messageSelector,
        找到的元素数量: potentialMessages.length,
        提示: '如果一直为0,说明选择器不匹配当前页面结构'
      });
    }

    // 临时存储所有找到的问题
    const foundQuestions = [];
    const seenTexts = new Set(); // 用于去重
    let filteredCount = 0; // 被过滤掉的消息数量

    for (let i = 0; i < potentialMessages.length; i++) {
      const element = potentialMessages[i];
      const textElement = currentConfig.textSelector
        ? element.querySelector(currentConfig.textSelector)
        : element;
      const text = getTextContent(textElement);

      // 如果文本内容有效且符合用户消息条件
      if (text && text.length > 2) {
        if (currentConfig.userCondition(element)) {
          // 使用文本内容进行去重
          if (!seenTexts.has(text)) {
            seenTexts.add(text);
            foundQuestions.push({ element, text });
          }
        } else {
          filteredCount++;
        }
      }
    }

    // 调试信息(仅在找到元素但没有用户消息时输出)
    if (potentialMessages.length > 0 && foundQuestions.length === 0) {
      console.log('[问题列表导航] 调试信息:', {
        网站: hostname,
        找到的消息元素: potentialMessages.length,
        通过用户条件的: foundQuestions.length,
        被过滤的: filteredCount,
        提示: '找到了消息元素,但 userCondition 过滤掉了所有消息。可能需要调整 userCondition 逻辑'
      });
    }

    // 更新全局问题列表
    questions = foundQuestions;

    // 确保排序正确
    if (isReversed) {
      questions.reverse();
    }

    // 更新界面
    updateQuestionCountDisplay();
    renderPage(currentPage);
    updatePagination();
  }

  // 改进的懒加载突破函数 - 使用脉冲式滚动和智能检测
  async function loadHistoryRecords() {
    if (isLoading) {
      // 如果正在加载,点击按钮可以停止加载
      isLoading = false;
      statusLabel.textContent = "已停止加载";
      setTimeout(() => {
        statusLabel.style.display = "none";
      }, 2000);
      return;
    }

    isLoading = true;
    statusLabel.textContent = "正在加载历史... (再次点击停止)";
    statusLabel.style.display = "block";

    // 智能查找滚动容器(排除侧边栏)
    function findScrollContainer() {
      // 辅助函数:判断是否是侧边栏(通常宽度较小,在左侧)
      function isSidebar(element) {
        const rect = element.getBoundingClientRect();
        const windowWidth = window.innerWidth;
        // 侧边栏特征:宽度小于窗口的30%,且在左侧
        return rect.width < windowWidth * 0.3 && rect.left < 100;
      }

      // 1. 尝试配置的选择器(支持多个选择器,用逗号分隔)
      const selectors = currentConfig.scrollContainerSelector.split(",");
      for (const selector of selectors) {
        const container = document.querySelector(selector.trim());
        if (
          container &&
          container.scrollHeight > container.clientHeight &&
          !isSidebar(container)
        ) {
          return container;
        }
      }

      // 2. 尝试常见的容器
      const commonSelectors = [
        "main",
        "#chat-history",
        '[class*="chat-content"]',
        '[class*="message-container"]',
        '[class*="chatContent"]',
      ];

      for (const selector of commonSelectors) {
        const container = document.querySelector(selector);
        if (
          container &&
          container.scrollHeight > container.clientHeight &&
          !isSidebar(container)
        ) {
          return container;
        }
      }

      // 3. 启发式查找:找到最后一条消息的可滚动父元素(排除侧边栏)
      const lastMessage = document.querySelector(
        currentConfig.messageSelector
      );
      if (lastMessage) {
        let parent = lastMessage.parentElement;
        while (parent && parent !== document.body) {
          const style = window.getComputedStyle(parent);
          if (
            (style.overflowY === "auto" || style.overflowY === "scroll") &&
            parent.scrollHeight > parent.clientHeight &&
            !isSidebar(parent)
          ) {
            return parent;
          }
          parent = parent.parentElement;
        }
      }

      // 4. 回退到 documentElement
      return document.documentElement;
    }

    const container = findScrollContainer();
    const originalScrollTop = container.scrollTop;
    const initialQuestionCount = questions.length;

    let consecutiveNoChange = 0;
    let lastHeight = container.scrollHeight;
    let lastQuestionCount = questions.length;
    let iteration = 0;
    const maxIterations = 20; // 最多尝试20次

    // 脉冲式滚动加载循环
    while (isLoading && consecutiveNoChange < 5 && iteration < maxIterations) {
      iteration++;

      // 1. 脉冲式滚动 - 模拟用户滚动行为
      // 先向下滚动一点,再滚动到顶部,触发懒加载机制
      container.scrollTop = Math.min(100, container.scrollHeight * 0.1);
      await new Promise((resolve) => setTimeout(resolve, 100));

      container.scrollTop = 0;
      await new Promise((resolve) => setTimeout(resolve, 100));

      // 2. 触发滚动事件(某些框架需要)
      container.dispatchEvent(new Event("scroll", { bubbles: true }));

      // 3. 动态等待 - 根据网络状况调整
      const waitTime = iteration < 5 ? 800 : 1200; // 前几次快速,后面慢一点
      await new Promise((resolve) => setTimeout(resolve, waitTime));

      // 4. 扫描新内容
      const preCount = questions.length;
      findAllQuestionsWithDeduplication();
      const postCount = questions.length;

      // 5. 检测变化
      const newHeight = container.scrollHeight;
      const heightChanged = newHeight > lastHeight;
      const questionsChanged = postCount > lastQuestionCount;

      if (heightChanged || questionsChanged) {
        // 发现新内容
        consecutiveNoChange = 0;
        lastHeight = newHeight;
        lastQuestionCount = postCount;

        statusLabel.textContent = `已加载 ${postCount} 个问题... (${iteration}/${maxIterations})`;
      } else {
        // 没有新内容
        consecutiveNoChange++;
        statusLabel.textContent = `检查中... (${consecutiveNoChange}/5) - 第${iteration}次`;
      }

      // 6. 额外的触发机制:模拟鼠标滚轮事件
      if (iteration % 3 === 0) {
        const wheelEvent = new WheelEvent("wheel", {
          deltaY: -100,
          bubbles: true,
          cancelable: true,
        });
        container.dispatchEvent(wheelEvent);
      }
    }

    // 恢复原始滚动位置
    container.scrollTop = originalScrollTop;

    // 完成加载
    const newQuestions = questions.length - initialQuestionCount;
    isLoading = false;
    autoLoadCompleted = true;

    statusLabel.textContent =
      newQuestions > 0
        ? `✓ 成功加载 ${newQuestions} 条新记录 (共${questions.length}条)`
        : iteration >= maxIterations
        ? "已达到最大尝试次数"
        : "未找到更多历史记录";

    // 延迟隐藏状态标签
    setTimeout(() => {
      statusLabel.style.display = "none";
    }, 4000);
  }

  // 使找到的问题定位在屏幕中
  function renderPage(page) {
    // 清空列表容器
    while (listContainer.firstChild) {
      listContainer.removeChild(listContainer.firstChild);
    }

    const start = (page - 1) * pageSize;
    const end = page * pageSize;
    const pageQuestions = questions.slice(start, end);

    pageQuestions.forEach((q, idx) => {
      const listItem = document.createElement("li");
      const shortText =
        q.text.substring(0, 20) + (q.text.length > 20 ? "..." : "");
      listItem.textContent = `${
        isReversed ? questions.length - start - idx : start + idx + 1
      }: ${shortText}`;
      listItem.style.padding = "8px 12px";
      listItem.style.cursor = "pointer";
      listItem.style.fontSize = "13px";
      listItem.style.color = colors.textPrimary;
      listItem.style.whiteSpace = "nowrap";
      listItem.style.overflow = "hidden";
      listItem.style.textOverflow = "ellipsis";
      listItem.style.borderBottom = `1px solid ${colors.itemBorder}`;
      listItem.style.transition = "background 0.2s";
      listItem.style.borderRadius = "4px";
      listItem.title = q.text;
      listItem.addEventListener("mouseover", () => {
        listItem.style.background = colors.itemHoverBg;
      });
      listItem.addEventListener("mouseout", () => {
        listItem.style.background = "none";
      });
      listItem.addEventListener("click", () => {
        q.element.scrollIntoView({ behavior: "smooth", block: "start" });
        floatWindow.style.opacity = "0";
        setTimeout(() => (floatWindow.style.display = "none"), 200);
        button.textContent = "问题列表";
      });
      listContainer.appendChild(listItem);
    });
  }

  // 更新分页控件
  function updatePagination() {
    // 清空分页容器
    while (paginationContainer.firstChild) {
      paginationContainer.removeChild(paginationContainer.firstChild);
    }

    const totalPages = Math.ceil(questions.length / pageSize);
    if (totalPages) {
      const prevButton = document.createElement("button");
      prevButton.textContent = "上一页";
      prevButton.style.padding = "5px 10px";
      prevButton.style.border = "none";
      prevButton.style.background = currentPage === 1 ? colors.paginationBg : colors.paginationActiveBg;
      prevButton.style.color = currentPage === 1 ? colors.textSecondary : "#fff";
      prevButton.style.cursor = currentPage === 1 ? "not-allowed" : "pointer";
      prevButton.style.borderRadius = "4px";
      prevButton.style.transition = "background 0.2s";
      prevButton.disabled = currentPage === 1;
      prevButton.addEventListener("click", () => {
        if (currentPage > 1) {
          currentPage--;
          renderPage(currentPage);
          updatePagination();
        }
      });
      paginationContainer.appendChild(prevButton);

      // 显示页码按钮,但限制最多显示5个
      const maxButtons = 5;
      let startPage = Math.max(
        1,
        Math.min(
          currentPage - Math.floor(maxButtons / 2),
          totalPages - maxButtons + 1
        )
      );
      if (startPage < 1) startPage = 1;
      const endPage = Math.min(startPage + maxButtons - 1, totalPages);

      if (startPage > 1) {
        const firstPageButton = document.createElement("button");
        firstPageButton.textContent = "1";
        firstPageButton.style.padding = "5px 10px";
        firstPageButton.style.border = "none";
        firstPageButton.style.background = colors.paginationBg;
        firstPageButton.style.color = colors.paginationColor;
        firstPageButton.style.cursor = "pointer";
        firstPageButton.style.borderRadius = "4px";
        firstPageButton.style.transition = "background 0.2s";
        firstPageButton.addEventListener("click", () => {
          currentPage = 1;
          renderPage(currentPage);
          updatePagination();
        });
        paginationContainer.appendChild(firstPageButton);

        if (startPage > 2) {
          const ellipsis = document.createElement("span");
          ellipsis.textContent = "...";
          ellipsis.style.padding = "5px";
          ellipsis.style.color = colors.textSecondary;
          paginationContainer.appendChild(ellipsis);
        }
      }

      for (let i = startPage; i <= endPage; i++) {
        const pageButton = document.createElement("button");
        pageButton.textContent = i;
        pageButton.style.padding = "5px 10px";
        pageButton.style.border = "none";
        pageButton.style.background = currentPage === i ? colors.paginationActiveBg : colors.paginationBg;
        pageButton.style.color = currentPage === i ? "#fff" : colors.paginationColor;
        pageButton.style.cursor = "pointer";
        pageButton.style.borderRadius = "4px";
        pageButton.style.transition = "background 0.2s";
        pageButton.addEventListener("click", () => {
          currentPage = i;
          renderPage(currentPage);
          updatePagination();
        });
        paginationContainer.appendChild(pageButton);
      }

      if (endPage < totalPages) {
        if (endPage < totalPages - 1) {
          const ellipsis = document.createElement("span");
          ellipsis.textContent = "...";
          ellipsis.style.padding = "5px";
          ellipsis.style.color = colors.textSecondary;
          paginationContainer.appendChild(ellipsis);
        }

        const lastPageButton = document.createElement("button");
        lastPageButton.textContent = totalPages;
        lastPageButton.style.padding = "5px 10px";
        lastPageButton.style.border = "none";
        lastPageButton.style.background = colors.paginationBg;
        lastPageButton.style.color = colors.paginationColor;
        lastPageButton.style.cursor = "pointer";
        lastPageButton.style.borderRadius = "4px";
        lastPageButton.style.transition = "background 0.2s";
        lastPageButton.addEventListener("click", () => {
          currentPage = totalPages;
          renderPage(currentPage);
          updatePagination();
        });
        paginationContainer.appendChild(lastPageButton);
      }

      const nextButton = document.createElement("button");
      nextButton.textContent = "下一页";
      nextButton.style.padding = "5px 10px";
      nextButton.style.border = "none";
      nextButton.style.background =
        currentPage === totalPages ? colors.paginationBg : colors.paginationActiveBg;
      nextButton.style.color = currentPage === totalPages ? colors.textSecondary : "#fff";
      nextButton.style.cursor =
        currentPage === totalPages ? "not-allowed" : "pointer";
      nextButton.style.borderRadius = "4px";
      nextButton.style.transition = "background 0.2s";
      nextButton.disabled = currentPage === totalPages;
      nextButton.addEventListener("click", () => {
        if (currentPage < totalPages) {
          currentPage++;
          renderPage(currentPage);
          updatePagination();
        }
      });
      paginationContainer.appendChild(nextButton);
    }
  }

  // 切换悬浮窗显示状态的函数
  function toggleFloatWindow() {
    if (
      floatWindow.style.display === "none" ||
      floatWindow.style.display === ""
    ) {
      findAllQuestionsWithDeduplication();
      updateFloatWindowPosition(); // 更新位置
      floatWindow.style.display = "block";
      floatWindow.style.opacity = "1";
      button.textContent = "隐藏列表";
    } else {
      floatWindow.style.opacity = "0";
      setTimeout(() => {
        floatWindow.style.display = "none";
        button.textContent = "问题列表";
      }, 200);
    }
  }
  
  // 注意:点击事件已在拖动逻辑中处理(mouseup 事件)

  // 监听用户输入新问题后触发查找
  function setupInputListener() {
    const input = document.querySelector(
      'textarea, input[type="text"], [contenteditable]'
    );
    if (input) {
      input.addEventListener("keypress", (e) => {
        if (e.key === "Enter" && !e.shiftKey) {
          setTimeout(findAllQuestionsWithDeduplication, 1000);
        }
      });
    }

    // 监听可能的发送按钮点击
    const sendButtons = document.querySelectorAll(
      'button[type="submit"], button[aria-label*="send"], button[aria-label*="发送"]'
    );
    sendButtons.forEach((btn) => {
      btn.addEventListener("click", () => {
        setTimeout(findAllQuestionsWithDeduplication, 1000);
      });
    });
  }

  // 页面加载后初始化
  window.addEventListener("load", () => {
    // 先找一次所有问题
    setTimeout(() => {
      findAllQuestionsWithDeduplication();
      setupInputListener();


    }, 1000);
  });

  // MutationObserver 监听DOM变化,动态更新问题列表
  const observerConfig = { childList: true, subtree: true };
  const observer = new MutationObserver((mutationsList) => {
    // 检查是否需要更新问题列表
    for (const mutation of mutationsList) {
      if (mutation.type === "childList" && mutation.addedNodes.length > 0) {
        // 检查是否添加了新的消息元素
        const hasNewMessages = Array.from(mutation.addedNodes).some((node) => {
          if (node.nodeType === Node.ELEMENT_NODE) {
            return (
              (node.matches && node.matches(currentConfig.messageSelector)) ||
              (node.querySelector &&
                node.querySelector(currentConfig.messageSelector))
            );
          }
          return false;
        });

        if (hasNewMessages) {
          // 使用节流技术避免频繁更新
          if (!observer.updateTimeout) {
            observer.updateTimeout = setTimeout(() => {
              findAllQuestionsWithDeduplication();
              observer.updateTimeout = null;
            }, 500);
          }
          break;
        }
      }
    }
  });

  // 开始观察DOM变化
  setTimeout(() => {
    const chatContainer =
      document.querySelector(".chat-container, #chat, main, article") ||
      document.body;
    observer.observe(chatContainer, observerConfig);
  }, 1500);
})();