您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
通过Ctrl+↑/↓快速调整输入光标所在的Tag权重,Ctrl+←/→移动Tag位置(使用时会自动格式化提示词,删除多余空格+逗号,自动在逗号后添加空格,不会删除换行)。支持所有Prompt输入框。
// ==UserScript== // @name NovelAI 快捷键权重调整 // @namespace https://novelai.net // @match https://novelai.net/image // @match https://novelai.github.io/image // @icon https://novelai.net/_next/static/media/goose_blue.1580a990.svg // @license MIT // @version 1.7 // @author Takoro // @description 通过Ctrl+↑/↓快速调整输入光标所在的Tag权重,Ctrl+←/→移动Tag位置(使用时会自动格式化提示词,删除多余空格+逗号,自动在逗号后添加空格,不会删除换行)。支持所有Prompt输入框。 // ==/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 updateInputContent(inputElement, newContent, selectionStart, selectionEnd) { 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, selectionStart); newRange.setEnd(inputElement.firstChild, selectionEnd); const selection = window.getSelection(); selection.removeAllRanges(); selection.addRange(newRange); const inputEvent = new Event('input', { bubbles: true }); inputElement.dispatchEvent(inputEvent); } // 移动标签函数 function moveTag(inputElement, tagInfo, direction) { const { tagText, fullText } = tagInfo; // 分割文本成标签数组,基于逗号和换行 const tags = fullText.split(/[\n,]+/).map(t => t.trim()).filter(t => t !== ''); // 找到当前标签的索引 const currentTag = tagText.trim(); const index = tags.findIndex(t => t === currentTag); if (index === -1) return; // 未找到标签 let newIndex; if (direction === -1) { // 向左移动 if (index === 0) return; // 已经在最左,无法移动 newIndex = index - 1; [tags[index], tags[newIndex]] = [tags[newIndex], tags[index]]; // 交换 } else if (direction === 1) { // 向右移动 if (index === tags.length - 1) return; // 已经在最右,无法移动 newIndex = index + 1; [tags[index], tags[newIndex]] = [tags[newIndex], tags[index]]; // 交换 } else { return; } // 重新构建文本,用逗号和空格连接 const newFullText = tags.join(', ') + ','; // 计算新文本中每个标签的位置 function getTagPositions(tags) { let currentPos = 0; const positions = []; for (let i = 0; i < tags.length; i++) { const start = currentPos; const end = start + tags[i].length; positions.push({ start, end }); currentPos = end; if (i < tags.length - 1) { currentPos += 2; // 添加 ', ' 的长度 } } // 添加最后一个逗号的位置 positions.push({ start: currentPos, end: currentPos + 1 }); return positions; } const positions = getTagPositions(tags); const newStart = positions[newIndex].start; const newEnd = positions[newIndex].end; // 更新输入框 updateInputContent(inputElement, newFullText, newStart, newEnd); } function handleKeydown(event) { const inputElement = getActiveInputElement(); if (!inputElement) return; if (!event.ctrlKey) return; if (event.key === 'ArrowUp' || event.key === 'ArrowDown') { 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); } else if (event.key === 'ArrowLeft' || event.key === 'ArrowRight') { event.preventDefault(); event.stopPropagation(); const tagInfo = getSelectedTagInfo(inputElement); if (!tagInfo) return; const direction = (event.key === 'ArrowLeft') ? -1 : 1; moveTag(inputElement, tagInfo, direction); } } 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(); } })();