您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Automatically clicks the send button in Sully Tavern when "Google AI Studio Candidate text empty" error occurs, and counts the auto-resends. Resets counter on manual send (click or Enter).
// ==UserScript== // @name SullyTavern Auto Resend on Empty AI // @namespace http://tampermonkey.net/ // @version 1.2 // @description Automatically clicks the send button in Sully Tavern when "Google AI Studio Candidate text empty" error occurs, and counts the auto-resends. Resets counter on manual send (click or Enter). // @author Your Helper // @match http://127.0.0.1:8000/* // @match http://localhost:8000/* // @match http://127.0.0.1:8000 // @match http://localhost:8000 // @grant none // @run-at document-start // ==/UserScript== (function() { 'use strict'; // --- КОНФИГУРАЦИЯ --- const TARGET_ERROR_SUBSTRING = "Google AI Studio Candidate text empty"; const BUTTON_ID = "send_but"; const TEXTAREA_ID = "send_textarea"; // <-- НОВОЕ: ID поля для ввода текста const SCRIPT_PREFIX = "[SullyTavern AutoResend] "; const COUNTER_ID = "st-autoresend-counter"; // --- СОСТОЯНИЕ СКРИПТА --- let scriptInitialized = false; let autoClickCount = 0; let counterElement = null; // --- НОВЫЕ ФУНКЦИИ ДЛЯ СЧЕТЧИКА --- /** * Создает и стилизует элемент счетчика на странице. */ function createCounterUI() { if (document.getElementById(COUNTER_ID)) return; counterElement = document.createElement('div'); counterElement.id = COUNTER_ID; Object.assign(counterElement.style, { position: 'fixed', bottom: '50px', left: '25px', color: '#888888', fontSize: '14px', fontWeight: 'bold', zIndex: '99999', pointerEvents: 'none', display: 'none' }); document.body.appendChild(counterElement); } /** * Обновляет отображение счетчика. Показывает его, если > 0, иначе скрывает. */ function updateCounterDisplay() { if (!counterElement) return; counterElement.textContent = autoClickCount; counterElement.style.display = autoClickCount > 0 ? 'block' : 'none'; } /** * Устанавливает слушатель на кнопку отправки для сброса счетчика при ручном клике. * @param {HTMLElement} button - Элемент кнопки отправки. */ function setupManualClickReset(button) { if (button.dataset.manualResetListener) return; button.addEventListener('click', (event) => { if (event.isTrusted && autoClickCount > 0) { console.log(SCRIPT_PREFIX + "Manual send detected (click). Resetting counter."); autoClickCount = 0; updateCounterDisplay(); } }); button.dataset.manualResetListener = 'true'; } /** * НОВАЯ ФУНКЦИЯ * Устанавливает слушатель на поле ввода текста для сброса счетчика при нажатии Enter. * @param {HTMLTextAreaElement} textarea - Элемент поля ввода. */ function setupEnterKeyReset(textarea) { if (textarea.dataset.enterResetListener) return; // Предотвращаем повторное назначение textarea.addEventListener('keydown', (event) => { // Сбрасываем, только если нажат Enter без Shift (для отправки) и счетчик > 0 if (event.key === 'Enter' && !event.shiftKey && autoClickCount > 0) { console.log(SCRIPT_PREFIX + "Manual send detected (Enter key). Resetting counter."); autoClickCount = 0; updateCounterDisplay(); } }); textarea.dataset.enterResetListener = 'true'; } // --- ОСНОВНАЯ ЛОГИКА --- function handleError(errorSource, errorDetails) { let errorMessage = ""; if (typeof errorDetails === 'string') { errorMessage = errorDetails; } else if (errorDetails instanceof Error) { errorMessage = errorDetails.message || ""; } else if (errorDetails && typeof errorDetails.reason !== 'undefined') { if (errorDetails.reason instanceof Error) { errorMessage = errorDetails.reason.message || ""; } else if (typeof errorDetails.reason === 'string') { errorMessage = errorDetails.reason; } } if (typeof errorMessage === 'string' && errorMessage.includes(TARGET_ERROR_SUBSTRING)) { console.log(SCRIPT_PREFIX + `Target error detected via ${errorSource}: "${errorMessage}". Clicking send button.`); clickSendButton(); } } function initializeScript() { if (scriptInitialized) { return; } createCounterUI(); if (typeof console !== 'undefined' && typeof console.error !== 'undefined') { const originalConsoleError = console.error; console.error = function(...args) { originalConsoleError.apply(console, args); if (args.length > 0) { handleError("console.error", args[0]); } }; } window.addEventListener('unhandledrejection', function(event) { handleError("unhandledrejection", event); }); // ИЗМЕНЕНО: MutationObserver теперь ищет и кнопку, и поле ввода const observer = new MutationObserver((mutations, obs) => { const sendButton = document.getElementById(BUTTON_ID); const textarea = document.getElementById(TEXTAREA_ID); let allElementsFound = true; if (sendButton && !sendButton.dataset.manualResetListener) { setupManualClickReset(sendButton); console.log(SCRIPT_PREFIX + "Manual click reset listener attached to the send button."); } else if (!sendButton) { allElementsFound = false; } if (textarea && !textarea.dataset.enterResetListener) { setupEnterKeyReset(textarea); console.log(SCRIPT_PREFIX + "Enter key reset listener attached to the textarea."); } else if (!textarea) { allElementsFound = false; } if (allElementsFound) { obs.disconnect(); // Все элементы найдены, прекращаем наблюдение console.log(SCRIPT_PREFIX + "All required UI elements found and listeners attached."); } }); observer.observe(document.body, { childList: true, subtree: true }); scriptInitialized = true; console.log(SCRIPT_PREFIX + "Script loaded and fully initialized."); } function clickSendButton() { setTimeout(() => { const sendButton = document.getElementById(BUTTON_ID); if (sendButton) { if (sendButton.offsetParent !== null && !sendButton.disabled) { autoClickCount++; updateCounterDisplay(); sendButton.click(); console.log(SCRIPT_PREFIX + `Send button clicked automatically. New count: ${autoClickCount}`); } else { console.warn(SCRIPT_PREFIX + `Send button found but is not clickable (Visible: ${sendButton.offsetParent !== null}, Disabled: ${sendButton.disabled}).`); } } else { console.warn(SCRIPT_PREFIX + `Send button with ID "${BUTTON_ID}" NOT FOUND.`); } }, 100); } // --- ЗАПУСК СКРИПТА --- if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initializeScript); } else { initializeScript(); } window.addEventListener('load', () => { if (!scriptInitialized) { initializeScript(); } }); })();