// ==UserScript== // @name NovelAI 快捷键权重调整 // @namespace https://novelai.net // @match https://novelai.net/image // @icon https://novelai.net/_next/static/media/goose_blue.1580a990.svg // @license MIT // @version 1.5 // @author Takoro // @description 通过Ctrl+↑/↓快速调整输入光标所在的Tag权重。支持所有Prompt输入框。 // @downloadURL https://update.greasyfork.icu/scripts/539015/NovelAI%20%E5%BF%AB%E6%8D%B7%E9%94%AE%E6%9D%83%E9%87%8D%E8%B0%83%E6%95%B4.user.js // @updateURL https://update.greasyfork.icu/scripts/539015/NovelAI%20%E5%BF%AB%E6%8D%B7%E9%94%AE%E6%9D%83%E9%87%8D%E8%B0%83%E6%95%B4.meta.js // ==/UserScript== (function() { 'use strict'; function getActiveInputElement() { const selection = window.getSelection(); if (!selection.rangeCount) return null; const node = selection.focusNode; const pElement = node.nodeType === 3 ? node.parentElement.closest('p') : node.closest('p'); if (pElement && ( pElement.closest('.prompt-input-box-prompt') || pElement.closest('.prompt-input-box-base-prompt') || pElement.closest('.prompt-input-box-negative-prompt') || pElement.closest('.prompt-input-box-undesired-content') || pElement.closest('[class*="character-prompt-input"]') )) { return pElement; } return null; } function getSelectedTagInfo(inputElement) { if (!inputElement) return null; const selection = window.getSelection(); if (!selection.rangeCount) return null; const range = selection.getRangeAt(0); const node = range.startContainer; const offset = range.startOffset; const fullText = inputElement.textContent || ''; let globalOffset = 0; if (node.nodeType === 3) { const treeWalker = document.createTreeWalker( inputElement, NodeFilter.SHOW_TEXT ); let currentNode; while ((currentNode = treeWalker.nextNode())) { if (currentNode === node) break; globalOffset += currentNode.length; } globalOffset += offset; } else { globalOffset = offset; } let start = globalOffset; while (start > 0 && fullText[start - 1] !== ',' && fullText[start - 1] !== '\n') { start--; } let end = globalOffset; while (end < fullText.length && fullText[end] !== ',' && fullText[end] !== '\n') { end++; } if (fullText[start] === ',' || fullText[start] === '\n') start++; if (fullText[end - 1] === ',') end--; const tagText = fullText.slice(start, end).trim(); return tagText ? { tagText, start, end, fullText } : null; } // 权重解析函数 function parseWeight(text) { const cleanText = text.replace(/:{2,}/g, '::').replace(/:+$/, ''); const weightMatch = cleanText.match(/^(-?[\d.]+)::(.+?)(?:::|$)/); if (weightMatch) { const weight = parseFloat(weightMatch[1]); return { weight: isNaN(weight) ? 1.0 : weight, tag: weightMatch[2].trim() }; } return { weight: 1.0, tag: text.trim() }; } function adjustWeight(text, direction) { const { weight, tag } = parseWeight(text); let newWeight = weight + (direction * 0.1); newWeight = Math.round(newWeight * 10) / 10; if (Math.abs(newWeight - 1.0) < 0.001) { return tag; } return `${newWeight}::${tag}::`; } function modifyInputText(inputElement, newText, start, end, fullText) { const newContent = fullText.slice(0, start) + newText + fullText.slice(end); if (inputElement.childNodes.length === 1 && inputElement.firstChild.nodeType === 3) { inputElement.firstChild.textContent = newContent; } else { const newTextNode = document.createTextNode(newContent); inputElement.innerHTML = ''; inputElement.appendChild(newTextNode); } const newRange = document.createRange(); newRange.setStart(inputElement.firstChild, start); newRange.setEnd(inputElement.firstChild, start + newText.length); const selection = window.getSelection(); selection.removeAllRanges(); selection.addRange(newRange); const inputEvent = new Event('input', { bubbles: true }); inputElement.dispatchEvent(inputEvent); } function handleKeydown(event) { const inputElement = getActiveInputElement(); if (!inputElement) return; if (!event.ctrlKey || (event.key !== 'ArrowUp' && event.key !== 'ArrowDown')) return; event.preventDefault(); event.stopPropagation(); const tagInfo = getSelectedTagInfo(inputElement); if (!tagInfo) return; const direction = (event.key === 'ArrowUp') ? 1 : -1; const newText = adjustWeight(tagInfo.tagText, direction); modifyInputText(inputElement, newText, tagInfo.start, tagInfo.end, tagInfo.fullText); } function init() { const checkInterval = setInterval(() => { const inputElement = getActiveInputElement(); if (inputElement) { clearInterval(checkInterval); document.addEventListener('keydown', handleKeydown, true); } }, 500); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();