您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
谷歌AI Studio 自动续写助手
当前为
// ==UserScript== // @name Google AI Studio auto-continue helper // @name:zh-CN Google AI Studio 自动续写助手 // @namespace http://tampermonkey.net/ // @version 4.0 (Advanced UI - Collapsible Panel) // @description auto-continue helper for Google AI Studio // @description:zh-CN 谷歌AI Studio 自动续写助手 // @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 isPanelExpanded = false; let customContinuePrompt = DEFAULT_CONTINUE_PROMPT; let debugPanel = null; let continueButton = null; let toggleButton = null; let customPromptInput = null; let uiContainer = null; let scrollTimeout = null; // --- 启动逻辑 --- const readyCheck = setInterval(() => { const scrollContainer = document.querySelector(SCROLL_CONTAINER_SELECTOR); if (scrollContainer) { clearInterval(readyCheck); console.log("Gemini 自动续写脚本 v4.0 (Advanced UI) 已启动!"); init(scrollContainer); } }, 1000); function init(scrollContainer) { createAdvancedUI(); scrollContainer.addEventListener("scroll", () => { clearTimeout(scrollTimeout); scrollTimeout = setTimeout(() => { updateDebugInfoAndTrigger(scrollContainer); }, 100); }); setInterval(syncUIState, 250); setTimeout(() => updateDebugInfoAndTrigger(scrollContainer), 500); } // --- 核心功能函数 --- 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 }) ); setTimeout(() => { const finalRunButton = document.querySelector(RUN_BUTTON_SELECTOR); if (finalRunButton && !finalRunButton.disabled) { finalRunButton.click(); console.log("消息已发送!"); } else { console.error("发送失败:按钮在填充输入后仍然被禁用。"); } }, 100); } else { console.warn("无法执行续写:缺少必要的UI组件。"); } } function updateDebugInfoAndTrigger(container) { if (!debugPanel) return; const allTurns = container.querySelectorAll(MESSAGE_TURN_SELECTOR); const total = allTurns.length; if (total === 0) { debugPanel.textContent = "0/0"; return; } let currentIndex = -1; const viewportTopThreshold = container.getBoundingClientRect().top + 60; for (let i = 0; i < allTurns.length; i++) { const rect = allTurns[i].getBoundingClientRect(); if (rect.top >= viewportTopThreshold) { currentIndex = i; break; } } if (currentIndex === -1) currentIndex = total - 1; debugPanel.textContent = `${currentIndex + 1}/${total}`; const shouldTrigger = isAutoContinueEnabled && total > 1 && currentIndex + 1 >= total - 1; if (shouldTrigger && !isCurrentlyFetching()) { console.log(`自动续写条件满足:位置 ${currentIndex + 1}/${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"); } } // --- 高级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 = "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); // 控制按钮区域 const controlSection = document.createElement("div"); controlSection.className = "panel-section"; // 手动继续按钮 continueButton = document.createElement("button"); continueButton.id = "tampermonkey-continue-btn"; continueButton.className = "control-button primary"; continueButton.textContent = "Continue"; continueButton.addEventListener("click", () => { console.log("手动触发续写..."); performAutoContinue(); }); // 自动续写开关 const autoToggleLabel = document.createElement("label"); autoToggleLabel.className = "switch-label"; autoToggleLabel.setAttribute("title", "Auto-Continue Toggle"); const autoContinueToggle = document.createElement("input"); autoContinueToggle.type = "checkbox"; autoContinueToggle.checked = isAutoContinueEnabled; autoContinueToggle.addEventListener("change", (e) => { isAutoContinueEnabled = e.target.checked; console.log(`自动续写已 ${isAutoContinueEnabled ? "开启" : "关闭"}`); }); const switchSlider = document.createElement("span"); switchSlider.className = "switch-slider"; autoToggleLabel.appendChild(autoContinueToggle); autoToggleLabel.appendChild(switchSlider); controlSection.appendChild(continueButton); controlSection.appendChild(autoToggleLabel); panel.appendChild(controlSection); // 自定义提示词区域 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: 20px; 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: 16px; 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; } .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); } .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; } .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); } .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; } /* 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; } } `); // 初始化面板状态 updatePanelState(); } })();