您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Formats narration and dialogues, and removes <think> tags.
// ==UserScript== // @name JanitorAI - Message Formatting Corrector (Mobile) // @namespace http://tampermonkey.net/ // @version 4.0 // @description Formats narration and dialogues, and removes <think> tags. // @author accforfaciet // @match *://janitorai.com/chats/* // @grant GM_addStyle // @run-at document-idle // @license MIT // ==/UserScript== (function() { 'use strict'; // --- DEBUG SETTINGS --- const DEBUG_MODE = true; // Set to true to output messages to the console and enable pauses const DEBUG_PAUSE_MS = 50; // Pause duration in milliseconds // --- END OF DEBUG SETTINGS --- // --- UNIVERSAL SELECTORS (work on both PC and mobile) --- const EDIT_BUTTON_SELECTOR = 'button[title="Edit Message"], button[aria-label="Edit"]'; const TEXT_AREA_SELECTOR = 'textarea[style*="font-size: 16px"][style*="!important"]'; const CONFIRM_BUTTON_SELECTOR = 'button[aria-label="Confirm"], button[aria-label="Save"]'; // --- END OF SETTINGS --- // --- DEBUG TOOLS --- /** Logs a message to the console only if DEBUG_MODE is enabled. */ function debugLog(...args) { if (DEBUG_MODE) { console.log('[DEBUG]', ...args); } } /** Creates a pause in execution only if DEBUG_MODE is enabled. */ function debugPause(ms = DEBUG_PAUSE_MS) { if (DEBUG_MODE) { debugLog(`Pausing for ${ms / 1000} sec...`); return new Promise(resolve => setTimeout(resolve, ms)); } return Promise.resolve(); } /** Highlights an element with a red border for visual debugging. */ function highlightElement(element, remove = false) { if (DEBUG_MODE && element) { element.style.outline = remove ? '' : '3px solid red'; element.style.outlineOffset = '3px'; } } /** * Asynchronous function to wait for an element to appear in the DOM. * @param {string} selector - CSS selector for the element. * @returns {Promise<Element>} */ function waitForElement(selector) { return new Promise(resolve => { const el = document.querySelector(selector); if (el) return resolve(el); const observer = new MutationObserver(() => { const el = document.querySelector(selector); if (el) { observer.disconnect(); resolve(el); } }); observer.observe(document.body, { childList: true, subtree: true }); }); } /** * Function #1: Removes text inside <think> tags. */ function removeThinkTags(text) { text = text.replace(/\n?\s*<thought>[\s\S]*?<\/thought>\s*\n?/g, ''); text = text.replace(/\n?\s*<thoughts>[\s\S]*?<\/thoughts>\s*\n?/g, ''); text = text.replace('<system>', ''); text = text.replace('<response>', ''); text = text.replace('</response>', ''); text = text.replace(/\n?\s*<think>[\s\S]*?<\/think>\s*\n?/g, ''); text = text.replace('</think>', ''); text = removeSystemPrompt(text); return text; } /** * Function #2: Smart text formatting (VERSION 4.0 - LINE-BY-LINE). * Correctly handles single-line paragraphs. */ function formatNarrationAndDialogue(text) { // 1. Pre-processing: remove <think> tags and normalize quotes. text = removeThinkTags(text); const normalizedText = text.replace(/[«“”„‟⹂❞❝]/g, '"'); const lines = normalizedText.split('\n'); const processedLines = lines.map(line => { const trimmedLine = line.trim(); if (trimmedLine === '') return ''; const cleanLine = trimmedLine.replace(/\*/g, ''); if (cleanLine.includes('"') || cleanLine.includes('`')) { const fragments = cleanLine.split(/("[\s\S]*?"|`[\s\S]*?`)/); const processedFragments = fragments.map(frag => { if ((frag.startsWith('"') && frag.endsWith('"')) || (frag.startsWith('`') && frag.endsWith('`'))) { return frag; } else if (frag.trim() !== '') { return `*${frag.trim()}*`; } return ''; }); return processedFragments.filter(f => f).join(' '); } else { return `*${cleanLine}*`; } }); return processedLines.join('\n'); } /** * Function #3: Removes the system prompt, if it exists. (VERSION 3.0 - IMPROVED) * Looks for a "join" of the "character*character" type, excluding other asterisks. */ function removeSystemPrompt(text) { const trimmedText = text.replace(' ', ''); // Check if the text starts with "The user" (case-insensitive) if (!trimmedText.toLowerCase().includes('theuser')) { debugLog('System prompt not found (text does not start with "The user"). No changes will be made.'); return text; // If not, do nothing } // IMPROVED: Look for a "join": [not a space or *] + [*] + [not a space or *] // [^\s\*] - means "any character that is not a (\s) space AND not an (\*) asterisk" const splitPointIndex = text.search(/[^\s\*]\*[^\s\*]/); if (splitPointIndex !== -1) { // If the point is found, cut everything before the * // +1 to find the position of the asterisk itself, not the character before it. const result = text.substring(splitPointIndex + 1); debugLog(`System prompt found. The text will be trimmed.`); return result; } debugLog('Text starts with "The user", but a join point (word*word) was not found. No changes will be made.'); return text; // If the point is not found, we don't change anything, just in case } /** * Main mechanism: clicks "Edit", processes the text, and saves. */ async function processLastMessage(textProcessor) { debugLog('--- STARTING EDIT PROCESS ---'); let lastHighlightedElement = null; // To remove the highlight const cleanup = () => { if (lastHighlightedElement) highlightElement(lastHighlightedElement, true); }; try { // 1. Searching for the "Edit" button debugLog('1. Searching for edit buttons with selector:', EDIT_BUTTON_SELECTOR); const allEditButtons = document.querySelectorAll(EDIT_BUTTON_SELECTOR); if (allEditButtons.length === 0) { debugLog('STOP: Edit buttons not found.'); return; } debugLog(`Found buttons: ${allEditButtons.length}. Selecting the last one.`); const lastEditButton = allEditButtons[allEditButtons.length - 1]; highlightElement(lastEditButton); lastHighlightedElement = lastEditButton; await debugPause(); // 2. Clicking the "Edit" button debugLog('2. Clicking the "Edit" button.'); lastEditButton.click(); await debugPause(500); // Short pause to let the DOM react // 3. Waiting for and finding the text area highlightElement(lastEditButton, true); // Remove highlight debugLog('3. Waiting for the text area to appear with selector:', TEXT_AREA_SELECTOR); const textField = await waitForElement(TEXT_AREA_SELECTOR); debugLog('Text area found!'); highlightElement(textField); lastHighlightedElement = textField; await debugPause(); // 4. Processing the text const originalText = textField.value; const newText = textProcessor(originalText); debugLog('4. Text processed.'); if (DEBUG_MODE) { console.groupCollapsed('[DEBUG] Text comparison (before and after)'); console.log('--- ORIGINAL TEXT ---\n', originalText); console.log('--- NEW TEXT ---\n', newText); console.groupEnd(); } // 5. Inserting the new text and simulating input debugLog('5. Inserting new text into the field.'); textField.value = newText; textField.dispatchEvent(new Event('input', { bubbles: true })); await debugPause(); // 6. Finding and clicking the "Confirm" button highlightElement(textField, true); // Remove highlight debugLog('6. Searching for the confirm button with selector:', CONFIRM_BUTTON_SELECTOR); const confirmButton = await waitForElement(CONFIRM_BUTTON_SELECTOR); debugLog('"Confirm" button found!'); highlightElement(confirmButton); lastHighlightedElement = confirmButton; await debugPause(); debugLog('7. Clicking the "Confirm" button.'); if (confirmButton) confirmButton.click(); debugLog('--- PROCESS COMPLETED SUCCESSFULLY ---'); } catch (error) { console.error('CRITICAL ERROR during the edit process:', error); } finally { cleanup(); // Remove highlight in any case } } /** * Creates and adds the button to the page. */ function createTriggerButtons() { const buttonContainer = document.createElement('div'); buttonContainer.id = 'janitor-editor-buttons'; document.body.appendChild(buttonContainer); const formatButton = document.createElement('button'); formatButton.innerHTML = '✏️'; formatButton.id = 'formatterTrigger'; formatButton.title = 'Format asterisks'; formatButton.addEventListener('click', () => processLastMessage(formatNarrationAndDialogue)); buttonContainer.appendChild(formatButton); } /** * Mobile keyboard fix: hides the buttons when typing. */ async function initKeyboardBugFix() { try { const mainInput = await waitForElement('textarea[placeholder^="Type a message"]'); const buttonContainer = document.getElementById('janitor-editor-buttons'); if (!mainInput || !buttonContainer) return; mainInput.addEventListener('focus', () => { buttonContainer.style.display = 'none'; }); mainInput.addEventListener('blur', () => { setTimeout(() => { buttonContainer.style.display = 'block'; }, 200); }); } catch (e) { console.log('Could not find the main input field for the keyboard bug fix (this is normal on PC).'); } } // --- STYLES --- // Use the desired block and comment out the other. // --- STYLES FOR PC (default) --- /* GM_addStyle(` #janitor-editor-buttons button { position: fixed; z-index: 9999; width: 50px; height: 50px; color: white; border: none; border-radius: 50%; font-size: 24px; box-shadow: 0 4px 8px rgba(0,0,0,0.3); cursor: pointer; transition: transform 0.2s; } #janitor-editor-buttons button:active { transform: scale(0.9); } #thinkRemoverTrigger { right: 27%; bottom: 5%; background-color: #6a22c9; } #formatterTrigger { right: 27%; bottom: 12%; background-color: #c9226e; } `); */ // --- STYLES FOR MOBILE --- // To use them: remove "/*" from the top and "*/" from the bottom of this block, // and wrap the top block with PC styles in the same comments. GM_addStyle(` #janitor-editor-buttons button { position: fixed; z-index: 9999; width: 40px; height: 40px; color: white; border: none; border-radius: 50%; font-size: 16px; box-shadow: 0 4px 8px rgba(0,0,0,0.3); cursor: pointer; transition: all 0.2s; } #janitor-editor-buttons button:active { transform: scale(0.9); } #thinkRemoverTrigger { right: 14%; bottom: 20%; background-color: #6a22c9; } #formatterTrigger { right: 28%; bottom: 20%; background-color: #c9226e; } `); // --- LAUNCH --- createTriggerButtons(); // The keyboard fix is activated automatically. // If you are on a PC, it simply won't find the input field and will exit quietly. initKeyboardBugFix(); console.log('Script "Message Formatting Corrector (Mobile) v4.0" launched successfully.'); debugLog('Script "Message Formatting Corrector (Mobile) v4.0-debug" launched successfully.'); })();