Greasy Fork

Greasy Fork is available in English.

webAI聊天问题列表导航

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

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

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴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);
})();