Google AI Studio 自动续写助手

谷歌AI Studio 自动续写助手 (支持Agent模式)

// ==UserScript==
// @name         Google AI Studio auto-continue helper
// @name:zh-CN   Google AI Studio 自动续写助手
// @namespace    http://tampermonkey.net/
// @version      5.2
// @description  auto-continue helper for Google AI Studio with Agent mode
// @description:zh-CN  谷歌AI Studio 自动续写助手 (支持Agent模式)
// @author       metrovoc
// @match        https://aistudio.google.com/prompts/*
// @grant        GM_addStyle
// @icon         https://www.google.com/s2/favicons?domain=aistudio.google.com
// @license      MIT
// ==/UserScript==

(function () {
  "use strict";

  // --- 配置区域 ---
  const SCROLL_CONTAINER_SELECTOR = "ms-autoscroll-container";
  const MESSAGE_TURN_SELECTOR = "ms-chat-turn";
  const AUTOSIZE_CONTAINER_SELECTOR = "ms-autosize-textarea";
  const TEXTAREA_SELECTOR = "ms-autosize-textarea textarea";
  const RUN_BUTTON_SELECTOR = 'run-button button[aria-label="Run"]';
  const STOP_BUTTON_SELECTOR = "run-button button.stoppable";
  const DEFAULT_CONTINUE_PROMPT = "continue";

  // --- 脚本状态变量 ---
  let isAutoContinueEnabled = false;
  let isAgentModeEnabled = false; // Agent模式开关
  let targetMessageCount = 10; // 目标消息数
  let isPanelExpanded = false;
  let customContinuePrompt = DEFAULT_CONTINUE_PROMPT;
  let debugPanel = null;
  let agentProgressPanel = null; // Agent进度显示
  let continueButton = null;
  let toggleButton = null;
  let customPromptInput = null;
  let targetCountInput = null; // 目标数量输入框
  let agentToggle = null; // Agent模式开关
  let uiContainer = null;
  let scrollTimeout = null;
  let agentInterval = null; // Agent模式定时器
  let countdownInterval = null; // 倒计时进度条定时器
  let countdownProgressBar = null; // 倒计时进度条元素
  let isAgentSectionExpanded = false; // Agent区域展开状态
  let currentScrollContainer = null;
  let containerWatcher = null;
  let lastMessageCount = 0; // 上次消息数量
  let pendingAgentTrigger = false; // 防止重复触发

  // --- 启动逻辑 ---
  console.log("Gemini 自动续写脚本 v4.2 (Agent Mode) 已启动!");
  createAdvancedUI();
  startContainerWatcher();

  // 调试函数 - 可在控制台手动调用
  window.debugAgentBreathing = function () {
    const agentSection = document.querySelector(".agent-section");
    if (agentSection) {
      agentSection.classList.toggle("breathing");
      console.log(
        "呼吸效果状态:",
        agentSection.classList.contains("breathing")
      );
    }
  };

  function startContainerWatcher() {
    // 持续监控容器的存在和变化
    containerWatcher = setInterval(() => {
      const scrollContainer = document.querySelector(SCROLL_CONTAINER_SELECTOR);

      // 检查容器是否发生变化
      if (scrollContainer !== currentScrollContainer) {
        if (currentScrollContainer) {
          console.log("检测到容器变化,重新绑定监听器");
          // 移除旧的监听器
          currentScrollContainer.removeEventListener("scroll", handleScroll);
        }

        if (scrollContainer) {
          console.log("找到新的滚动容器,绑定监听器");
          currentScrollContainer = scrollContainer;
          bindScrollListener(scrollContainer);
          // 立即更新一次状态
          setTimeout(() => updateDebugInfoAndTrigger(scrollContainer), 100);
        } else {
          console.log("未找到滚动容器");
          currentScrollContainer = null;
          if (debugPanel) {
            debugPanel.textContent = "...";
          }
          if (agentProgressPanel) {
            agentProgressPanel.textContent = "0/0";
          }
        }
      }

      // Agent模式独立更新
      if (isAgentModeEnabled && scrollContainer) {
        updateAgentProgress(scrollContainer);
      } else if (isAgentModeEnabled) {
        // 即使没有容器也要更新视觉状态
        updateAgentVisualState();
      }

      // 每次都检查视觉状态,确保及时清除
      updateAgentVisualState();

      // 同步UI状态,确保continue button在fetching时被禁用
      syncUIState();
    }, 500);
  }

  function handleScroll() {
    clearTimeout(scrollTimeout);
    scrollTimeout = setTimeout(() => {
      if (currentScrollContainer) {
        updateDebugInfoAndTrigger(currentScrollContainer);
      }
    }, 100);
  }

  function bindScrollListener(scrollContainer) {
    scrollContainer.addEventListener("scroll", handleScroll);
  }

  // --- Agent模式相关函数 ---
  function updateAgentProgress(container) {
    const validTurns = getValidChatTurns(container);
    const currentCount = validTurns.length;

    if (agentProgressPanel) {
      agentProgressPanel.textContent = `${currentCount}/${targetMessageCount}`;
    }

    // 更新Agent UI状态
    updateAgentVisualState();

    // 检查是否需要触发Agent自动续写
    if (
      isAgentModeEnabled &&
      currentCount < targetMessageCount &&
      !isCurrentlyFetching() &&
      !pendingAgentTrigger
    ) {
      // 检查消息数是否有增加(说明AI已经回复完成)
      if (currentCount > lastMessageCount || lastMessageCount === 0) {
        lastMessageCount = currentCount;
        scheduleAgentContinue();
      }
    } else if (currentCount >= targetMessageCount) {
      stopAgentMode();
    }
  }

  function updateAgentVisualState() {
    const agentSection = document.querySelector(".agent-section");
    if (!agentSection) return;

    // 如果Agent模式未启用,强制清除呼吸效果
    if (!isAgentModeEnabled) {
      agentSection.classList.remove("breathing");
      return;
    }

    const isGenerating = isCurrentlyFetching();
    const isCountingDown = pendingAgentTrigger;

    console.log(
      `视觉状态更新: Agent=${isAgentModeEnabled}, 生成中=${isGenerating}, 倒计时=${isCountingDown}`
    );

    // 只在AI正在生成且不在倒计时时显示呼吸效果
    if (isGenerating && !isCountingDown) {
      agentSection.classList.add("breathing");
    } else {
      agentSection.classList.remove("breathing");
    }
  }

  function scheduleAgentContinue() {
    // 随机间隔: 2-8秒,防止风控
    const randomDelay = Math.random() * 6000 + 2000; // 2000-8000ms
    console.log(
      `Agent模式: 将在 ${(randomDelay / 1000).toFixed(1)} 秒后继续生成`
    );

    pendingAgentTrigger = true;
    updateAgentVisualState(); // 更新视觉状态
    startCountdownProgress(randomDelay);

    agentInterval = setTimeout(() => {
      if (isAgentModeEnabled && !isCurrentlyFetching()) {
        console.log("Agent模式: 执行自动续写");
        performAutoContinue();
      }
      pendingAgentTrigger = false;
      updateAgentVisualState(); // 更新视觉状态
      hideCountdownProgress();
    }, randomDelay);
  }

  function startCountdownProgress(totalDelay) {
    if (!countdownProgressBar) return;

    countdownProgressBar.style.display = "block";
    const startTime = Date.now();
    const updateInterval = 50; // 每50ms更新一次

    countdownInterval = setInterval(() => {
      const elapsed = Date.now() - startTime;
      const progress = Math.min((elapsed / totalDelay) * 100, 100);

      const progressFill = countdownProgressBar.querySelector(".progress-fill");
      if (progressFill) {
        progressFill.style.width = `${progress}%`;
      }

      if (progress >= 100) {
        clearInterval(countdownInterval);
        countdownInterval = null;
      }
    }, updateInterval);
  }

  function hideCountdownProgress() {
    if (countdownProgressBar) {
      countdownProgressBar.style.display = "none";
      const progressFill = countdownProgressBar.querySelector(".progress-fill");
      if (progressFill) {
        progressFill.style.width = "0%";
      }
    }
    if (countdownInterval) {
      clearInterval(countdownInterval);
      countdownInterval = null;
    }
  }

  function startAgentMode() {
    console.log(`Agent模式启动: 目标 ${targetMessageCount} 条消息`);
    isAgentModeEnabled = true;
    lastMessageCount = 0;
    pendingAgentTrigger = false;

    updateAgentProgressVisibility();

    // 立即检查一次状态
    if (currentScrollContainer) {
      updateAgentProgress(currentScrollContainer);
    }
  }

  function stopAgentMode() {
    console.log("Agent模式已停止");
    isAgentModeEnabled = false;
    pendingAgentTrigger = false;
    lastMessageCount = 0;

    if (agentInterval) {
      clearTimeout(agentInterval);
      agentInterval = null;
    }

    hideCountdownProgress();
    updateAgentProgressVisibility();

    // 强制清除呼吸效果
    const agentSection = document.querySelector(".agent-section");
    if (agentSection) {
      agentSection.classList.remove("breathing");
    }
    updateAgentVisualState();

    if (agentToggle) {
      agentToggle.checked = false;
    }
  }

  function updateAgentProgressVisibility() {
    if (agentProgressPanel) {
      const agentStatusSection = agentProgressPanel.closest(".panel-section");
      if (agentStatusSection) {
        agentStatusSection.style.display = isAgentModeEnabled ? "flex" : "none";
      }
    }
  }

  // 页面卸载时清理
  window.addEventListener("beforeunload", () => {
    stopAgentMode();
    if (containerWatcher) {
      clearInterval(containerWatcher);
    }
    if (countdownInterval) {
      clearInterval(countdownInterval);
    }
  });

  // --- 核心功能函数 ---
  function isCurrentlyFetching() {
    return !!document.querySelector(STOP_BUTTON_SELECTOR);
  }

  function syncUIState() {
    if (continueButton) {
      continueButton.disabled = isCurrentlyFetching();
    }
  }

  function performAutoContinue() {
    if (isCurrentlyFetching()) {
      console.log("正在等待AI响应,续写操作已跳过。");
      return;
    }

    const autosizeContainer = document.querySelector(
      AUTOSIZE_CONTAINER_SELECTOR
    );
    const textarea = document.querySelector(TEXTAREA_SELECTOR);
    const runButton = document.querySelector(RUN_BUTTON_SELECTOR);

    if (autosizeContainer && textarea && runButton) {
      console.log(`尝试执行续写,发送: "${customContinuePrompt}"`);

      autosizeContainer.setAttribute("data-value", customContinuePrompt);
      textarea.value = customContinuePrompt;
      textarea.dispatchEvent(
        new Event("input", { bubbles: true, composed: true })
      );

      // Generate random delay between 50ms and 250ms to simulate human behavior
      const humanDelay = Math.random() * 200 + 50; // 50-250ms
      console.log(
        `Human-like delay: ${humanDelay.toFixed(
          0
        )}ms before clicking run button`
      );

      setTimeout(() => {
        // Re-check conditions before clicking to avoid conflicts during delay
        if (isCurrentlyFetching()) {
          console.log(
            "AI started responding during delay, skipping button click"
          );
          return;
        }

        const finalRunButton = document.querySelector(RUN_BUTTON_SELECTOR);
        if (finalRunButton && !finalRunButton.disabled) {
          finalRunButton.click();
          console.log("消息已发送!");
          // 立即同步UI状态,禁用continue button
          setTimeout(() => syncUIState(), 100);
        } else {
          console.error("发送失败:按钮在填充输入后仍然被禁用。");
        }
      }, humanDelay);
    } else {
      console.warn("无法执行续写:缺少必要的UI组件。");
    }
  }

  // 过滤有效的聊天轮次:包含model-prompt-container且不含ms-thought-chunk的ms-chat-turn
  function getValidChatTurns(container) {
    const allTurns = container.querySelectorAll(MESSAGE_TURN_SELECTOR);
    const validTurns = [];

    console.log(`[getValidChatTurns] 总聊天轮次: ${allTurns.length}`);

    allTurns.forEach((turn, index) => {
      // 检查是否包含 model-prompt-container
      const hasModelPromptContainer = turn.querySelector(
        ".model-prompt-container"
      );
      if (!hasModelPromptContainer) {
        console.log(
          `[getValidChatTurns] 轮次 ${
            index + 1
          }: 跳过 - 无 model-prompt-container`
        );
        return;
      }

      // 检查是否不包含 ms-thought-chunk (自定义元素,不用点号)
      const hasThoughtChunk = turn.querySelector("ms-thought-chunk");
      if (hasThoughtChunk) {
        console.log(
          `[getValidChatTurns] 轮次 ${index + 1}: 跳过 - 包含 ms-thought-chunk`
        );
        return;
      }

      console.log(`[getValidChatTurns] 轮次 ${index + 1}: ✓ 有效`);
      validTurns.push(turn);
    });

    console.log(
      `[getValidChatTurns] 有效聊天轮次: ${validTurns.length}/${allTurns.length}`
    );
    return validTurns;
  }

  function updateDebugInfoAndTrigger(container) {
    if (!debugPanel || !container) return;

    const validTurns = getValidChatTurns(container);
    const total = validTurns.length;
    if (total === 0) {
      debugPanel.textContent = "0/0";
      return;
    }

    let currentIndex = -1;
    const viewportTopThreshold = container.getBoundingClientRect().top + 60;
    for (let i = 0; i < validTurns.length; i++) {
      const rect = validTurns[i].getBoundingClientRect();
      if (rect.top >= viewportTopThreshold) {
        currentIndex = i;
        break;
      }
    }

    // 计算显示位置:
    // currentIndex = -1 表示所有validturn都已滚动过,显示最后一个
    // currentIndex = 0 表示还没开始看第一个validturn,显示0
    // currentIndex = i 表示正在看第i个validturn,显示i
    let displayPosition;
    if (currentIndex === -1) {
      displayPosition = total; // 所有都看过了
    } else if (currentIndex === 0) {
      displayPosition = 0; // 还没开始看第一个
    } else {
      displayPosition = currentIndex; // 正在看第currentIndex个(从0开始)
    }

    debugPanel.textContent = `${displayPosition}/${total}`;

    // 只有在非Agent模式下才使用滚动逻辑触发
    if (!isAgentModeEnabled) {
      const shouldTrigger =
        isAutoContinueEnabled && total > 1 && displayPosition >= total - 1;
      if (shouldTrigger && !isCurrentlyFetching()) {
        console.log(`自动续写条件满足:位置 ${displayPosition}/${total}`);
        performAutoContinue();
      }
    }
  }

  // --- SVG图标创建函数 ---
  function createSVGIcon(pathData, viewBox = "0 0 24 24") {
    const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
    svg.setAttribute("width", "20");
    svg.setAttribute("height", "20");
    svg.setAttribute("viewBox", viewBox);
    svg.setAttribute("fill", "currentColor");

    const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
    path.setAttribute("d", pathData);

    svg.appendChild(path);
    return svg;
  }

  function createExpandIcon() {
    return createSVGIcon("M7 14l5-5 5 5z");
  }

  function createCollapseIcon() {
    return createSVGIcon("M7 10l5 5 5-5z");
  }

  function createSettingsIcon() {
    return createSVGIcon(
      "M12 15.5A3.5 3.5 0 0 1 8.5 12A3.5 3.5 0 0 1 12 8.5a3.5 3.5 0 0 1 3.5 3.5 3.5 3.5 0 0 1-3.5 3.5m7.43-2.53c.04-.32.07-.64.07-.97 0-.33-.03-.66-.07-1l2.11-1.63c.19-.15.24-.42.12-.64l-2-3.46c-.12-.22-.39-.31-.61-.22l-2.49 1c-.52-.39-1.06-.73-1.69-.98l-.37-2.65A.506.506 0 0 0 14 2h-4c-.25 0-.46.18-.5.42l-.37 2.65c-.63.25-1.17.59-1.69.98l-2.49-1c-.22-.09-.49 0-.61.22l-2 3.46c-.13.22-.07.49.12.64L4.57 11c-.04.34-.07.67-.07 1 0 .33.03.65.07.97l-2.11 1.66c-.19.15-.25.42-.12.64l2 3.46c.12.22.39.3.61.22l2.49-1.01c.52.4 1.06.74 1.69.99l.37 2.65c.04.24.25.42.5.42h4c.25 0 .46-.18.5-.42l.37-2.65c.63-.26 1.17-.59 1.69-.99l2.49 1.01c.22.08.49 0 .61-.22l2-3.46c.12-.22.07-.49-.12-.64l-2.11-1.66Z"
    );
  }

  function togglePanel() {
    isPanelExpanded = !isPanelExpanded;
    updatePanelState();
  }

  function updatePanelState() {
    const panel = document.getElementById("tampermonkey-panel");
    const toggleBtnIcon = document.getElementById("tampermonkey-toggle-icon");

    if (isPanelExpanded) {
      panel.style.display = "flex";
      // 清空并重新添加icon
      toggleBtnIcon.textContent = "";
      toggleBtnIcon.appendChild(createCollapseIcon());
      uiContainer.classList.add("expanded");
    } else {
      panel.style.display = "none";
      // 清空并重新添加icon
      toggleBtnIcon.textContent = "";
      toggleBtnIcon.appendChild(createSettingsIcon());
      uiContainer.classList.remove("expanded");
    }
  }

  function toggleAgentSection() {
    isAgentSectionExpanded = !isAgentSectionExpanded;
    const agentDetails = document.querySelector(".agent-details");
    const expandArrow = document.querySelector(".expand-arrow");

    if (agentDetails && expandArrow) {
      if (isAgentSectionExpanded) {
        agentDetails.style.display = "block";
        expandArrow.textContent = "▼";
        expandArrow.classList.add("expanded");
      } else {
        agentDetails.style.display = "none";
        expandArrow.textContent = "▶";
        expandArrow.classList.remove("expanded");
      }
    }
  }

  // --- 高级UI创建 ---
  function createAdvancedUI() {
    // 主容器
    uiContainer = document.createElement("div");
    uiContainer.id = "tampermonkey-ui-container";
    uiContainer.className = "collapsed";
    document.body.appendChild(uiContainer);

    // 切换按钮(始终可见)
    toggleButton = document.createElement("button");
    toggleButton.id = "tampermonkey-toggle-btn";
    toggleButton.className = "toggle-button";
    toggleButton.setAttribute("title", "Toggle Gemini Assistant Panel");

    const toggleIcon = document.createElement("span");
    toggleIcon.id = "tampermonkey-toggle-icon";
    toggleIcon.appendChild(createSettingsIcon());
    toggleButton.appendChild(toggleIcon);

    toggleButton.addEventListener("click", togglePanel);
    uiContainer.appendChild(toggleButton);

    // 主面板容器(可折叠)
    const panel = document.createElement("div");
    panel.id = "tampermonkey-panel";
    panel.className = "main-panel";
    panel.style.display = "none";
    uiContainer.appendChild(panel);

    // 状态显示区域
    const statusSection = document.createElement("div");
    statusSection.className = "panel-section";

    const statusLabel = document.createElement("span");
    statusLabel.className = "section-label";
    statusLabel.textContent = "Scroll Position:";

    debugPanel = document.createElement("div");
    debugPanel.id = "tampermonkey-debug-panel";
    debugPanel.className = "status-display";
    debugPanel.textContent = "...";

    statusSection.appendChild(statusLabel);
    statusSection.appendChild(debugPanel);
    panel.appendChild(statusSection);

    // Agent模式状态区域(初始隐藏)
    const agentStatusSection = document.createElement("div");
    agentStatusSection.className = "panel-section";
    agentStatusSection.style.display = "none";

    const agentStatusLabel = document.createElement("span");
    agentStatusLabel.className = "section-label";
    agentStatusLabel.textContent = "Agent Progress:";

    agentProgressPanel = document.createElement("div");
    agentProgressPanel.id = "tampermonkey-agent-panel";
    agentProgressPanel.className = "status-display agent-status";
    agentProgressPanel.textContent = "0/0";

    agentStatusSection.appendChild(agentStatusLabel);
    agentStatusSection.appendChild(agentProgressPanel);
    panel.appendChild(agentStatusSection);

    // 控制按钮区域
    const controlSection = document.createElement("div");
    controlSection.className = "panel-section compact";

    // 手动继续按钮行
    const continueRow = document.createElement("div");
    continueRow.className = "control-row";

    continueButton = document.createElement("button");
    continueButton.id = "tampermonkey-continue-btn";
    continueButton.className = "control-button primary compact";
    continueButton.textContent = "Continue";
    continueButton.addEventListener("click", () => {
      console.log("手动触发续写...");
      performAutoContinue();
      // 更新Agent视觉状态,因为可能开始了新的生成
      setTimeout(() => {
        updateAgentVisualState();
        syncUIState();
      }, 100);
    });

    continueRow.appendChild(continueButton);

    // 自动续写开关行
    const autoRow = document.createElement("div");
    autoRow.className = "control-row";

    const autoLabel = document.createElement("label");
    autoLabel.className = "control-label";
    autoLabel.textContent = "Auto continue:";

    const autoSwitchContainer = document.createElement("div");
    autoSwitchContainer.className = "switch-container";

    const autoSwitchLabel = document.createElement("label");
    autoSwitchLabel.className = "switch-label small";
    autoSwitchLabel.setAttribute("title", "Auto-Continue Toggle");

    const autoContinueToggle = document.createElement("input");
    autoContinueToggle.type = "checkbox";
    autoContinueToggle.checked = isAutoContinueEnabled;
    autoContinueToggle.addEventListener("change", (e) => {
      isAutoContinueEnabled = e.target.checked;
      // 开启普通自动续写时,关闭Agent模式
      if (isAutoContinueEnabled && isAgentModeEnabled) {
        stopAgentMode();
      }
      console.log(`自动续写已 ${isAutoContinueEnabled ? "开启" : "关闭"}`);
    });

    const switchSlider = document.createElement("span");
    switchSlider.className = "switch-slider";

    autoSwitchLabel.appendChild(autoContinueToggle);
    autoSwitchLabel.appendChild(switchSlider);
    autoSwitchContainer.appendChild(autoSwitchLabel);

    autoRow.appendChild(autoLabel);
    autoRow.appendChild(autoSwitchContainer);

    controlSection.appendChild(continueRow);
    controlSection.appendChild(autoRow);
    panel.appendChild(controlSection);

    // Agent模式控制区域
    const agentSection = document.createElement("div");
    agentSection.className = "panel-section agent-section";

    // Agent模式主标题和展开箭头
    const agentMainHeader = document.createElement("div");
    agentMainHeader.className = "agent-main-header";
    agentMainHeader.addEventListener("click", toggleAgentSection);

    const agentMainTitle = document.createElement("span");
    agentMainTitle.className = "section-label clickable";
    agentMainTitle.textContent = "Agent Mode";

    const expandArrow = document.createElement("span");
    expandArrow.className = "expand-arrow";
    expandArrow.textContent = "▶";

    agentMainHeader.appendChild(agentMainTitle);
    agentMainHeader.appendChild(expandArrow);

    // Agent详细控制区域(可折叠)
    const agentDetailsSection = document.createElement("div");
    agentDetailsSection.className = "agent-details";
    agentDetailsSection.style.display = "none";

    // Agent模式开关行
    const agentToggleRow = document.createElement("div");
    agentToggleRow.className = "agent-control-row";

    const agentToggleLabel = document.createElement("label");
    agentToggleLabel.className = "control-label";
    agentToggleLabel.textContent = "Enable:";

    const agentSwitchContainer = document.createElement("div");
    agentSwitchContainer.className = "switch-container";

    const agentSwitchLabel = document.createElement("label");
    agentSwitchLabel.className = "switch-label small";
    agentSwitchLabel.setAttribute("title", "Agent Mode Toggle");

    agentToggle = document.createElement("input");
    agentToggle.type = "checkbox";
    agentToggle.checked = isAgentModeEnabled;
    agentToggle.addEventListener("change", (e) => {
      if (e.target.checked) {
        // 开启Agent模式时,关闭普通自动续写
        if (isAutoContinueEnabled) {
          isAutoContinueEnabled = false;
          const normalToggle = document.querySelector(
            '#tampermonkey-panel input[type="checkbox"]'
          );
          if (normalToggle && normalToggle !== agentToggle) {
            normalToggle.checked = false;
          }
        }
        startAgentMode();
      } else {
        stopAgentMode();
      }
    });

    const agentSwitchSlider = document.createElement("span");
    agentSwitchSlider.className = "switch-slider";

    agentSwitchLabel.appendChild(agentToggle);
    agentSwitchLabel.appendChild(agentSwitchSlider);
    agentSwitchContainer.appendChild(agentSwitchLabel);

    agentToggleRow.appendChild(agentToggleLabel);
    agentToggleRow.appendChild(agentSwitchContainer);

    // 目标数量输入行
    const targetRow = document.createElement("div");
    targetRow.className = "agent-control-row";

    const targetLabel = document.createElement("label");
    targetLabel.className = "control-label";
    targetLabel.setAttribute("for", "tampermonkey-target-input");
    targetLabel.textContent = "Target:";

    targetCountInput = document.createElement("input");
    targetCountInput.id = "tampermonkey-target-input";
    targetCountInput.type = "number";
    targetCountInput.className = "number-input compact";
    targetCountInput.value = targetMessageCount;
    targetCountInput.min = "1";
    targetCountInput.max = "999";
    targetCountInput.placeholder = "10";
    targetCountInput.addEventListener("input", (e) => {
      const value = parseInt(e.target.value);
      if (value && value > 0) {
        targetMessageCount = value;
        console.log(`目标消息数已更新: ${targetMessageCount}`);
      }
    });

    targetRow.appendChild(targetLabel);
    targetRow.appendChild(targetCountInput);

    // 倒计时进度条
    countdownProgressBar = document.createElement("div");
    countdownProgressBar.className = "countdown-progress";
    countdownProgressBar.style.display = "none";

    const progressLabel = document.createElement("div");
    progressLabel.className = "progress-label";
    progressLabel.textContent = "Next generation in:";

    const progressBarContainer = document.createElement("div");
    progressBarContainer.className = "progress-bar-container";

    const progressFill = document.createElement("div");
    progressFill.className = "progress-fill";

    progressBarContainer.appendChild(progressFill);
    countdownProgressBar.appendChild(progressLabel);
    countdownProgressBar.appendChild(progressBarContainer);

    agentDetailsSection.appendChild(agentToggleRow);
    agentDetailsSection.appendChild(targetRow);
    agentDetailsSection.appendChild(countdownProgressBar);

    agentSection.appendChild(agentMainHeader);
    agentSection.appendChild(agentDetailsSection);
    panel.appendChild(agentSection);

    // 自定义提示词区域
    const promptSection = document.createElement("div");
    promptSection.className = "panel-section";

    const promptLabel = document.createElement("label");
    promptLabel.className = "section-label";
    promptLabel.setAttribute("for", "tampermonkey-prompt-input");
    promptLabel.textContent = "Custom Prompt:";

    customPromptInput = document.createElement("input");
    customPromptInput.id = "tampermonkey-prompt-input";
    customPromptInput.type = "text";
    customPromptInput.className = "prompt-input";
    customPromptInput.value = customContinuePrompt;
    customPromptInput.placeholder = "Enter custom continue prompt...";
    customPromptInput.addEventListener("input", (e) => {
      customContinuePrompt = e.target.value.trim() || DEFAULT_CONTINUE_PROMPT;
      console.log(`自定义提示词已更新: "${customContinuePrompt}"`);
    });

    promptSection.appendChild(promptLabel);
    promptSection.appendChild(customPromptInput);
    panel.appendChild(promptSection);

    // 添加样式
    GM_addStyle(`
      #tampermonkey-ui-container {
        position: fixed;
        bottom: 20px;
        right: 20px;
        z-index: 9999;
        display: flex;
        flex-direction: column;
        align-items: flex-end;
        gap: 12px;
        font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
      }

      .toggle-button {
        width: 48px;
        height: 48px;
        border: none;
        border-radius: 50%;
        background: linear-gradient(135deg, #1a73e8, #185abc);
        color: white;
        cursor: pointer;
        display: flex;
        align-items: center;
        justify-content: center;
        box-shadow: 0 4px 12px rgba(26, 115, 232, 0.3);
        transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
        backdrop-filter: blur(10px);
      }

      .toggle-button:hover {
        transform: translateY(-2px);
        box-shadow: 0 6px 20px rgba(26, 115, 232, 0.4);
        background: linear-gradient(135deg, #185abc, #1557a0);
      }

      .toggle-button:active {
        transform: translateY(0);
      }

      #tampermonkey-toggle-icon {
        display: flex;
        align-items: center;
        justify-content: center;
        transition: transform 0.3s ease;
      }

      .expanded #tampermonkey-toggle-icon {
        transform: rotate(180deg);
      }

      .main-panel {
        background: rgba(255, 255, 255, 0.95);
        backdrop-filter: blur(20px);
        border-radius: 16px;
        padding: 16px;
        min-width: 280px;
        box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
        border: 1px solid rgba(255, 255, 255, 0.2);
        display: none;
        flex-direction: column;
        gap: 12px;
        animation: slideIn 0.3s cubic-bezier(0.4, 0, 0.2, 1);
      }

      @keyframes slideIn {
        from {
          opacity: 0;
          transform: translateY(20px) scale(0.95);
        }
        to {
          opacity: 1;
          transform: translateY(0) scale(1);
        }
      }

      .panel-section {
        display: flex;
        flex-direction: column;
        gap: 8px;
      }

      .panel-section.compact {
        gap: 6px;
      }

      .control-row {
        display: flex;
        justify-content: space-between;
        align-items: center;
        min-height: 32px;
      }

      .section-label {
        font-size: 12px;
        font-weight: 600;
        color: #5f6368;
        text-transform: uppercase;
        letter-spacing: 0.5px;
      }

      .status-display {
        background: rgba(26, 115, 232, 0.1);
        color: #1a73e8;
        padding: 8px 12px;
        border-radius: 8px;
        font-family: 'SF Mono', 'Monaco', 'Cascadia Code', monospace;
        font-size: 14px;
        font-weight: 600;
        text-align: center;
        border: 1px solid rgba(26, 115, 232, 0.2);
      }

      .agent-status {
        background: rgba(34, 139, 34, 0.1);
        color: #228b22;
        border: 1px solid rgba(34, 139, 34, 0.2);
      }

      .control-button {
        background: linear-gradient(135deg, #1a73e8, #185abc);
        color: white;
        border: none;
        padding: 12px 20px;
        border-radius: 10px;
        font-size: 14px;
        font-weight: 600;
        cursor: pointer;
        transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
        box-shadow: 0 2px 8px rgba(26, 115, 232, 0.3);
      }

      .control-button:hover {
        transform: translateY(-1px);
        box-shadow: 0 4px 12px rgba(26, 115, 232, 0.4);
      }

      .control-button:disabled {
        background: #e0e0e0;
        color: #9e9e9e;
        cursor: not-allowed;
        transform: none;
        box-shadow: none;
      }

      .control-button.compact {
        padding: 8px 16px;
        font-size: 13px;
        width: 100%;
      }



      .switch-label {
        position: relative;
        display: inline-block;
        width: 52px;
        height: 28px;
        cursor: pointer;
        align-self: flex-start;
      }

      .switch-label input {
        opacity: 0;
        width: 0;
        height: 0;
      }

      .switch-slider {
        position: absolute;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        background-color: #ccc;
        border-radius: 28px;
        transition: 0.4s cubic-bezier(0.4, 0, 0.2, 1);
      }

      .switch-slider:before {
        position: absolute;
        content: "";
        height: 20px;
        width: 20px;
        left: 4px;
        bottom: 4px;
        background-color: white;
        border-radius: 50%;
        transition: 0.4s cubic-bezier(0.4, 0, 0.2, 1);
        box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
      }

      .switch-label input:checked + .switch-slider {
        background: linear-gradient(135deg, #1a73e8, #185abc);
      }

      .switch-label input:checked + .switch-slider:before {
        transform: translateX(24px);
      }

      .switch-label.small {
        width: 40px;
        height: 22px;
      }

      .switch-label.small .switch-slider {
        border-radius: 22px;
      }

      .switch-label.small .switch-slider:before {
        height: 16px;
        width: 16px;
        left: 3px;
        bottom: 3px;
      }

      .switch-label.small input:checked + .switch-slider:before {
        transform: translateX(18px);
      }

      .prompt-input {
        width: 100%;
        padding: 12px 16px;
        border: 2px solid #e0e0e0;
        border-radius: 10px;
        font-size: 14px;
        font-family: inherit;
        transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
        background: rgba(255, 255, 255, 0.8);
        box-sizing: border-box;
      }

      .prompt-input:focus {
        outline: none;
        border-color: #1a73e8;
        box-shadow: 0 0 0 3px rgba(26, 115, 232, 0.1);
        background: white;
      }

      .prompt-input::placeholder {
        color: #9e9e9e;
        font-style: italic;
      }

      .agent-section {
        border-top: 1px solid #e0e0e0;
        background: rgba(34, 139, 34, 0.03);
        border-radius: 10px;
        padding: 10px;
        margin-top: 4px;
        transition: all 0.3s ease;
        position: relative;
        overflow: hidden;
      }

      .agent-section.breathing {
        animation: agentBreathing 2.2s ease-in-out infinite;
      }

      .agent-section.breathing::before {
        content: '';
        position: absolute;
        top: 2px;
        left: 2px;
        right: 2px;
        bottom: 2px;
        background: radial-gradient(circle at center, rgba(34, 139, 34, 0.4) 0%, rgba(34, 139, 34, 0.2) 40%, transparent 70%);
        border-radius: inherit;
        transform: scale(0);
        animation: rippleBreathing 2.2s ease-in-out infinite;
        pointer-events: none;
        z-index: 1;
      }

      .agent-section.breathing::after {
        content: '';
        position: absolute;
        top: 4px;
        left: 4px;
        right: 4px;
        bottom: 4px;
        background: radial-gradient(circle at center, rgba(34, 139, 34, 0.5) 0%, rgba(34, 139, 34, 0.3) 30%, transparent 60%);
        border-radius: inherit;
        transform: scale(0);
        animation: rippleBreathing 2.2s ease-in-out infinite 0.6s;
        pointer-events: none;
        z-index: 1;
      }

      .agent-section.breathing > * {
        position: relative;
        z-index: 2;
      }

      @keyframes agentBreathing {
        0%, 100% {
          background: rgba(34, 139, 34, 0.03);
          box-shadow: 0 0 0 0 rgba(34, 139, 34, 0.1), 
                      0 0 15px 0 rgba(34, 139, 34, 0.05);
          border-color: #e0e0e0;
          transform: scale(1);
        }
        50% {
          background: rgba(34, 139, 34, 0.12);
          box-shadow: 0 0 0 4px rgba(34, 139, 34, 0.2), 
                      0 0 25px 5px rgba(34, 139, 34, 0.15);
          border-color: rgba(34, 139, 34, 0.4);
          transform: scale(1.02);
        }
      }

      @keyframes rippleBreathing {
        0%, 100% {
          transform: scale(0);
          opacity: 0;
        }
        15% {
          transform: scale(0.5);
          opacity: 0.8;
        }
        35% {
          transform: scale(1.2);
          opacity: 0.7;
        }
        60% {
          transform: scale(1.8);
          opacity: 0.4;
        }
        85% {
          transform: scale(2.2);
          opacity: 0.1;
        }
        100% {
          transform: scale(2.5);
          opacity: 0;
        }
      }

      .agent-main-header {
        display: flex;
        justify-content: space-between;
        align-items: center;
        cursor: pointer;
        padding: 4px 0;
        transition: all 0.2s ease;
      }

      .agent-main-header:hover {
        background: rgba(34, 139, 34, 0.1);
        border-radius: 6px;
        padding: 4px 8px;
      }

      .section-label.clickable {
        user-select: none;
        transition: color 0.2s ease;
      }

      .expand-arrow {
        font-size: 12px;
        transition: transform 0.3s ease;
        color: #228b22;
        font-weight: bold;
      }

      .expand-arrow.expanded {
        transform: rotate(0deg);
      }

      .agent-details {
        margin-top: 8px;
        padding-top: 8px;
        border-top: 1px solid rgba(34, 139, 34, 0.2);
        display: none;
      }

      .agent-control-row {
        display: flex;
        justify-content: space-between;
        align-items: center;
        margin-bottom: 8px;
        min-height: 28px;
      }

      .control-label {
        font-size: 12px;
        font-weight: 600;
        color: #5f6368;
        min-width: 60px;
      }

      .switch-container {
        display: flex;
        align-items: center;
      }

      .input-label {
        font-size: 12px;
        font-weight: 600;
        color: #5f6368;
        text-transform: uppercase;
        letter-spacing: 0.5px;
        margin-bottom: 4px;
        display: block;
      }

      .number-input {
        width: 100%;
        padding: 10px 14px;
        border: 2px solid #e0e0e0;
        border-radius: 8px;
        font-size: 14px;
        font-family: inherit;
        transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
        background: rgba(255, 255, 255, 0.8);
        box-sizing: border-box;
        text-align: center;
        font-weight: 600;
      }

      .number-input:focus {
        outline: none;
        border-color: #228b22;
        box-shadow: 0 0 0 3px rgba(34, 139, 34, 0.1);
        background: white;
      }

      .number-input.compact {
        width: 80px;
        padding: 8px 12px;
        font-size: 13px;
      }



      .countdown-progress {
        background: rgba(34, 139, 34, 0.1);
        border-radius: 8px;
        padding: 8px;
        margin: 6px 0;
        border: 1px solid rgba(34, 139, 34, 0.2);
      }

      .progress-label {
        font-size: 11px;
        color: #228b22;
        font-weight: 600;
        margin-bottom: 6px;
        text-transform: uppercase;
        letter-spacing: 0.5px;
      }

      .progress-bar-container {
        width: 100%;
        height: 4px;
        background: rgba(34, 139, 34, 0.2);
        border-radius: 2px;
        overflow: hidden;
      }

      .progress-fill {
        height: 100%;
        background: linear-gradient(90deg, #228b22, #32cd32);
        border-radius: 2px;
        width: 0%;
        transition: width 0.1s ease-out;
      }

      /* Dark mode support */
      @media (prefers-color-scheme: dark) {
        .main-panel {
          background: rgba(32, 33, 36, 0.95);
          border: 1px solid rgba(255, 255, 255, 0.1);
        }
        
        .section-label {
          color: #bdc1c6;
        }
        
        .prompt-input {
          background: rgba(32, 33, 36, 0.8);
          border-color: #5f6368;
          color: #e8eaed;
        }
        
        .prompt-input:focus {
          background: #32333;
          border-color: #1a73e8;
        }

        .agent-section {
          background: rgba(34, 139, 34, 0.08);
          border-top: 1px solid #5f6368;
        }

        .agent-section.breathing::before {
          background: radial-gradient(circle at center, rgba(74, 222, 128, 0.4) 0%, rgba(34, 139, 34, 0.2) 40%, transparent 70%);
        }

        .agent-section.breathing::after {
          background: radial-gradient(circle at center, rgba(74, 222, 128, 0.5) 0%, rgba(34, 139, 34, 0.3) 30%, transparent 60%);
        }

        @keyframes agentBreathing {
          0%, 100% {
            background: rgba(34, 139, 34, 0.08);
            box-shadow: 0 0 0 0 rgba(34, 139, 34, 0.15), 
                        0 0 20px 0 rgba(34, 139, 34, 0.1);
            border-color: #5f6368;
            transform: scale(1);
          }
          50% {
            background: rgba(34, 139, 34, 0.20);
            box-shadow: 0 0 0 4px rgba(34, 139, 34, 0.35), 
                        0 0 30px 8px rgba(34, 139, 34, 0.25);
            border-color: rgba(74, 222, 128, 0.5);
            transform: scale(1.02);
          }
        }

        .agent-main-header:hover {
          background: rgba(34, 139, 34, 0.15);
        }

        .agent-details {
          border-top: 1px solid rgba(34, 139, 34, 0.3);
        }

        .input-label, .control-label {
          color: #bdc1c6;
        }

        .number-input {
          background: rgba(32, 33, 36, 0.8);
          border-color: #5f6368;
          color: #e8eaed;
        }

        .number-input:focus {
          background: #323336;
          border-color: #228b22;
        }

        .countdown-progress {
          background: rgba(34, 139, 34, 0.15);
          border-color: rgba(34, 139, 34, 0.3);
        }

        .progress-label {
          color: #4ade80;
        }

        .progress-bar-container {
          background: rgba(34, 139, 34, 0.3);
        }
      }
    `);

    // 初始化面板状态
    updatePanelState();
  }
})();

QingJ © 2025

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