NAI Prompt Linker

Import prompts into NovelAI from other editor

当前为 2024-02-20 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name NAI Prompt Linker
  3. // @namespace https://github.com/cpuopt/NAI-Prompt-Linker
  4. // @version 1.0.4
  5. // @description Import prompts into NovelAI from other editor
  6. // @author cpufan
  7. // @license GPL-3.0 License
  8. // @include *://*:7860/*
  9. // @include *://*:17860/*
  10. // @match https://novelai.net/image
  11. // @icon https://www.google.com/s2/favicons?sz=64&domain=novelai.net
  12. // @grant GM_setValue
  13. // @grant GM_addValueChangeListener
  14. // @run-at document-end
  15. // @supportURL https://github.com/cpuopt/NAI-Prompt-Linker/issues
  16. // ==/UserScript==
  17.  
  18. (function () {
  19. 'use strict';
  20.  
  21. if (window.location.href.startsWith("https://novelai.net/image")) {
  22.  
  23. console.debug("NAI端加载成功")
  24. var opInterval = 200
  25. var insertText = (area, text) => {
  26. area.focus()
  27. document.execCommand('selectAll');
  28. document.execCommand('delete');
  29. document.execCommand('insertText', false, text);
  30. area.blur()
  31. }
  32.  
  33. var click_generate = () => {
  34. return new Promise(resolve => {
  35. setTimeout(() => {
  36. document.evaluate("//span[contains(text(),'Generate ') and contains(text(),' Image')]/..", document.body, null, 9, null).singleNodeValue.click();
  37. resolve();
  38. }, opInterval);
  39. });
  40. }
  41.  
  42. var insertPrompt = (prompt) => {
  43. return new Promise(resolve => {
  44. setTimeout(() => {
  45. document.evaluate("//button[text()='Prompt']", document.body, null, 9, null).singleNodeValue.click();
  46. setTimeout(() => {
  47. insertText(document.querySelector("textarea[placeholder='Write your prompt here. Use tags to sculpt your outputs.']"), prompt)
  48. }, opInterval);
  49. resolve();
  50. }, opInterval);
  51. });
  52.  
  53. }
  54.  
  55. var insertUndesiredContent = (uprompt) => {
  56. return new Promise(resolve => {
  57. setTimeout(() => {
  58. document.evaluate("//button[text()='Undesired Content']", document.body, null, 9, null).singleNodeValue.click();
  59. setTimeout(() => {
  60. insertText(document.querySelector("textarea[placeholder='Write what you want removed from the generation.']"), uprompt)
  61. }, opInterval);
  62. resolve();
  63. }, opInterval);
  64. });
  65.  
  66. }
  67.  
  68. async function generate(prompt, uprompt, generate) {
  69. await insertUndesiredContent(uprompt)
  70. await insertPrompt(prompt)
  71. if (generate == true) {
  72. await click_generate()
  73. }
  74. }
  75.  
  76. let NAI_save = GM_addValueChangeListener("->NAI", function (key, oldValue, newValue, remote) {
  77. console.debug(key + ":\n" + oldValue + "=>" + newValue);
  78.  
  79. if (newValue != null) {
  80.  
  81. console.debug(newValue.msg, newValue.time, newValue.prompt.prompt, newValue.prompt.uprompt)
  82. generate(newValue.prompt.prompt, newValue.prompt.uprompt, newValue.generate)
  83.  
  84. GM_setValue("NAI->", { time: newValue.time })
  85. }
  86.  
  87. });
  88.  
  89.  
  90.  
  91. }
  92. else if (/^(http)|(https):\/\/(localhost)|(127.0.0.1):(7860)|(17860)\*+/.test(window.location.href)) {
  93.  
  94. var pluginCanvas
  95. var sendPrompt
  96. window.onload = function () {
  97. //菜单初始化
  98. pluginCanvas = new Canvas()
  99. sendPrompt = new SendPrompt()
  100. }
  101.  
  102. class SendPrompt {
  103. button;
  104. last_time;
  105. constructor() {
  106. this.button = document.createElement('button')
  107. this.button.className = 'plugin-button-blue'
  108. this.button.id = 'SendPromptButton'
  109. this.button.innerText = '发送Prompt到NAI'
  110. this.button.setAttribute('onclick', `window.sendPrompt2NAI()`)
  111.  
  112. const canvas = document.querySelector('#plugin-canvas')
  113. canvas.appendChild(this.button)
  114.  
  115. let NAI_send = GM_addValueChangeListener("NAI->", function (key, oldValue, newValue, remote) {
  116. console.debug(newValue);
  117. if (newValue.time == sendPrompt.last_time) {
  118.  
  119. sendPrompt.button.className = 'plugin-button-white'
  120. sendPrompt.button.innerText = '发送成功'
  121.  
  122. setTimeout(() => {
  123. sendPrompt.button.className = 'plugin-button-blue'
  124. sendPrompt.button.innerText = '发送Prompt到NAI'
  125. }, 2000)
  126. }
  127.  
  128. });
  129.  
  130. }
  131. /**
  132. * 发送Prompt到NAI
  133. */
  134. send() {
  135.  
  136. let prompt = document.querySelector("#txt2img_prompt > label > textarea").value
  137. let uprompt = document.querySelector("#txt2img_neg_prompt > label > textarea").value
  138. this.last_time = Number(Date.now())
  139. console.debug("setValue", prompt, uprompt, this.last_time)
  140. GM_setValue("->NAI", { msg: "", prompt: { prompt: prompt, uprompt: uprompt }, generate: false, time: this.last_time })
  141. }
  142.  
  143.  
  144. }
  145.  
  146.  
  147. /**
  148. * 插件菜单类
  149. */
  150. class Canvas {
  151.  
  152. constructor() {
  153. var styles = document.createElement('style');
  154. document.head.appendChild(styles);
  155. styles.innerHTML = `
  156. #plugin-canvas {transition: right 0.6s;position: fixed;background-color: #ffffff;right: 0;top: 5px;height: 75px;border-radius: 10px;border-left: solid 2px rgb(162, 218, 255)}
  157. .plugin-button-blue {display: block;color: rgb(255, 255, 255);background-color: rgb(0, 150, 250);font-size: 14px;font-weight: bold;margin: 16px;padding-left: 20px;padding-right: 20px;height: 42px;border: none;border-radius: 100000px;transition: background-color 0.6s;}
  158. .plugin-button-white {display: block;color: rgb(71,71,71);background-color: rgb(245,245,245);font-size: 14px;font-weight: bold;margin: 16px;padding-left: 20px;padding-right: 20px;height: 42px;border: none;border-radius: 100000px;transition: background-color 0.6s;}
  159. .plugin-button-blue:hover {background-color: rgb(0,114,240);cursor: pointer;}
  160. .plugin-button-white:hover {background-color: rgb(235,235,235);cursor: pointer;}
  161. .hideCanvas {padding: 0;height: 60px;width: 15px;position: absolute;left: -15px;top: 6px;border: solid 2px rgb(162, 218, 255);background-color: rgb(255, 255, 255);border-radius: 10px 0px 0px 10px;transition: background-color 0.6s;cursor: pointer;}
  162. .hideCanvas:hover{background-color: rgb(162, 218, 255);}
  163. .showCanvas {padding: 0;height: 60px;width: 15px;position: absolute;left: -15px;top: 6px;border: solid 2px rgb(162, 218, 255);background-color: rgb(255, 255, 255);border-radius: 10px 0px 0px 10px;transition: background-color 0.6s;cursor: pointer;}
  164. .showCanvas:hover{background-color: rgb(162, 218, 255);}
  165. `
  166. let canvas = document.createElement('div')
  167. canvas.id = 'plugin-canvas'
  168.  
  169. let canvasButton = document.createElement('button')
  170. canvas.appendChild(canvasButton)
  171. canvasButton.id = 'canvasButton'
  172. canvasButton.setAttribute('onclick', `window.hideCanvas()`)
  173. canvasButton.className = 'hideCanvas'
  174. canvasButton.innerHTML = '<svg style="margin-left:-2px" width="16" height="16" fill="rgb(0,150,250)" class="bi bi-caret-right-fill" viewBox="0 0 16 16"><path d="m12.14 8.753-5.482 4.796c-.646.566-1.658.106-1.658-.753V3.204a1 1 0 0 1 1.659-.753l5.48 4.796a1 1 0 0 1 0 1.506z"/></svg>'
  175.  
  176. document.body.appendChild(canvas)
  177.  
  178. }
  179. /**
  180. * 显示插件菜单
  181. */
  182. show() {
  183. document.querySelector('#plugin-canvas').style.right = '0px'
  184.  
  185. document.querySelector('#canvasButton').className = 'hideCanvas'
  186. document.querySelector('#canvasButton').innerHTML = `<svg style="margin-left:-2px" width="16" height="16" fill="rgb(0,150,250)" class="bi bi-caret-right-fill" viewBox="0 0 16 16">
  187. <path d="m12.14 8.753-5.482 4.796c-.646.566-1.658.106-1.658-.753V3.204a1 1 0 0 1 1.659-.753l5.48 4.796a1 1 0 0 1 0 1.506z"/>
  188. </svg>`
  189. document.querySelector('#canvasButton').setAttribute('onclick', `window.hideCanvas()`)
  190.  
  191. }
  192. /**
  193. * 隐藏插件菜单
  194. */
  195. hide() {
  196. document.querySelector('#plugin-canvas').style.right = '-171.6px'
  197.  
  198. document.querySelector('#canvasButton').className = 'showCanvas'
  199. document.querySelector('#canvasButton').innerHTML = `<svg style="margin-left:-2px" width="16" height="16" fill="rgb(0,150,250)" class="bi bi-caret-left-fill" viewBox="0 0 16 16">
  200. <path d="m3.86 8.753 5.482 4.796c.646.566 1.658.106 1.658-.753V3.204a1 1 0 0 0-1.659-.753l-5.48 4.796a1 1 0 0 0 0 1.506z"/>
  201. </svg>`
  202. document.querySelector('#canvasButton').setAttribute('onclick', `window.showCanvas()`)
  203.  
  204. }
  205.  
  206. }
  207. // 显示插件菜单
  208. unsafeWindow.showCanvas = function () {
  209. pluginCanvas.show()
  210. GM_setValue('CanvasState', 'S')
  211. }
  212. // 隐藏插件菜单
  213. unsafeWindow.hideCanvas = function () {
  214. pluginCanvas.hide()
  215. GM_setValue('CanvasState', 'H')
  216. }
  217. // 发送Prompt到NAI
  218. unsafeWindow.sendPrompt2NAI = function () {
  219. sendPrompt.send()
  220. }
  221.  
  222. }
  223.  
  224.  
  225.  
  226. })();