ChatGPT-input-helper

Help organize commonly used spells quickly

当前为 2023-03-03 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name ChatGPT-input-helper
  3. // @name:zh-TW ChatGPT-input-helper 快速輸入常用咒文
  4. // @namespace https://github.com/we684123/ChatGPT-input-helper
  5. // @version 0.0.3
  6. // @author we684123
  7. // @description Help organize commonly used spells quickly
  8. // @description:zh-TW 幫助快速組織常用咒文
  9. // @license MIT
  10. // @icon https://chat.openai.com/favicon.ico
  11. // @match https://chat.openai.com/chat
  12. // @match https://chat.openai.com/chat/*
  13. // @grant GM_getValue
  14. // @grant GM_setValue
  15. // @grant GM_deleteValue
  16. // @run-at document-end
  17. // ==/UserScript==
  18.  
  19.  
  20. // 定義常用咒文
  21. let customize
  22.  
  23. // 定義初始化咒文
  24. let init_customize = [
  25. {
  26. name: '繁體中文初始化', // 按鈕的顯示名稱
  27. position: 'start', // start = 最前面 , end = 最後面
  28. autoEnter: true, // 是否自動按下 Enter
  29. content: [ // 要被放入輸入框的文字
  30. `以下問答請使用繁體中文,並使用台灣用語。\n`,
  31. ].join("")
  32. }, {
  33. name: '請繼續', // 按鈕的顯示名稱
  34. position: 'start', // start = 最前面 , end = 最後面
  35. autoEnter: true, // 是否自動按下 Enter
  36. content: [ // 要被放入輸入框的文字
  37. `請繼續`,
  38. ].join("")
  39. }, {
  40. name: '請從""繼續', // 按鈕的顯示名稱
  41. position: 'start', // start = 最前面 , end = 最後面
  42. autoEnter: false, // 是否自動按下 Enter
  43. content: [ // 要被放入輸入框的文字
  44. `請從""繼續`,
  45. ].join("")
  46. }
  47. ];
  48.  
  49. // 定義雜七雜八
  50. const HELPER_MENU_TEST = 'input helper'; // 按鈕文字
  51. const CONTAINER_CLASS = 'helper_textcontainer'; // 按鈕用容器
  52. const MAIN_BUTTON_CLASS = 'main_button'; // 選單按鈕
  53. const SETTING_BUTTON_CLASS = 'setting_button'; // 控制按鈕
  54. const MENU_CLASS = 'main_menu'; // 選單
  55.  
  56. const NAV_SELECTOR = 'nav.flex.h-full.flex-1.flex-col.space-y-1.p-2'; // 左邊選單的定位(整體)
  57. const NAV_MENU = 'nav > div.overflow-y-auto'; // 左邊選單的定位(上層)
  58. const TEXT_INPUTBOX_POSITION = 'textarea.m-0'; // 輸入框的定位
  59. const SUBMIT_BUTTON_POSITION = 'button.absolute'; // 送出按鈕的定位
  60. const INPUT_EVENT = new Event('input', { bubbles: true }); // 模擬輸入於輸入框的事件
  61.  
  62.  
  63. // 準備 function
  64. const insertCustomize = (customize, name) => {
  65. // customize = 設定的object
  66. // name = 觸發按鈕的名稱
  67.  
  68. console.log('insertCustomize 開始執行');
  69. const textInputbox = document.querySelector(TEXT_INPUTBOX_POSITION);
  70. const submitButton = document.querySelector(SUBMIT_BUTTON_POSITION);
  71. const item = customize.find(i => i.name === name);
  72.  
  73. if (item) {
  74. console.log(`已找到名稱為 ${name} 的元素`);
  75.  
  76. // start = 最前面 , end = 最後面
  77. // 有想要出游標定位,之後再說
  78. // 例如 `請從 "" 繼續輸出` 就希望定在 "" 之間
  79. if (item.position === 'start') {
  80. textInputbox.value = item.content + textInputbox.value;
  81. } else {
  82. textInputbox.value += item.content;
  83. }
  84.  
  85. // 模擬,讓對話框可以自動更新高度
  86. textInputbox.dispatchEvent(INPUT_EVENT);
  87. textInputbox.focus();
  88.  
  89. console.log(`textInputbox.value =\n${textInputbox.value}`);
  90.  
  91. // 如果需要,自動按下 Enter
  92. if (item.autoEnter) {
  93. console.log('autoEnter (O)');
  94. submitButton.click();
  95. }
  96.  
  97. console.log('====== insertCustomize 結束 ======');
  98. } else {
  99. console.error(`找不到名稱為 ${name} 的元素`);
  100. }
  101. };
  102.  
  103.  
  104. const buttonStyles = {
  105. [`.${CONTAINER_CLASS}`]: {
  106. position: 'relative',
  107. display: 'flex',
  108. alignItems: 'center',
  109. justifyContent: 'center',
  110. borderBottom: '1px solid rgba(255, 255, 255, 0.2)',
  111. border: '1px solid #fff',
  112. borderRadius: '5px',
  113. width: '100%',
  114. boxSizing: 'border-box',
  115. },
  116. [`.${MAIN_BUTTON_CLASS}`]: {
  117. padding: '8px 12px',
  118. fontSize: '14px',
  119. color: '#fff',
  120. border: '1px solid #fff',
  121. backgroundColor: '#525252',
  122. borderRadius: '5px',
  123. cursor: 'pointer',
  124. width: '85%',
  125. boxSizing: 'border-box',
  126. margin: '0 auto', // 水平置中
  127. },
  128. [`.${SETTING_BUTTON_CLASS}`]: {
  129. padding: '8px 12px',
  130. fontSize: '14px',
  131. color: '#fff',
  132. backgroundColor: '#525252',
  133. border: 'none',
  134. borderRadius: '5px',
  135. cursor: 'pointer',
  136. width: '15%',
  137. boxSizing: 'border-box',
  138. },
  139. [`.${MENU_CLASS}`]: {
  140. display: 'none',
  141. position: 'absolute',
  142. left: '100%',
  143. top: 0,
  144. zIndex: 1,
  145. width: '100%',
  146. backgroundColor: '#2b2c2f', // 新增背景色設定
  147. border: '1px solid #fff', // 新增邊框設定
  148. borderRadius: '15px', // 新增圓角設定
  149. },
  150. [`.${MENU_CLASS} button`]: {
  151. display: 'block',
  152. width: '100%',
  153. height: '100%',
  154. padding: '8px 12px',
  155. fontSize: '14px',
  156. color: '#fff',
  157. backgroundColor: '#2b2c2f',
  158. border: '1px solid #fff',
  159. borderRadius: '5px', // 圓角
  160. cursor: 'pointer',
  161. },
  162. };
  163.  
  164.  
  165.  
  166. const createMenu = (containerNode) => {
  167. const menu = document.createElement('div');
  168. menu.id = 'helper_menu';
  169. menu.classList.add(MENU_CLASS);
  170. menu.style.display = 'none';
  171. menu.style.width = `${containerNode.offsetWidth}px`
  172.  
  173. customize.forEach(element => {
  174. const menuItem = createMenuItem(element);
  175. menu.appendChild(menuItem);
  176. });
  177.  
  178. return menu;
  179. };
  180.  
  181. const createMenuItem = (element) => {
  182. const menuItem = document.createElement('button');
  183. menuItem.innerText = element.name;
  184. menuItem.id = element.name;
  185. menuItem.addEventListener('click', (event) => {
  186. console.log('按鈕資訊:', event.target);
  187. insertCustomize(customize, event.target.id);
  188. });
  189.  
  190. return menuItem;
  191. };
  192. const addButton = (containerNode, customize, buttonText = 'Click Me') => {
  193. const createMainButton = () => {
  194. const mainButton = document.createElement('button');
  195. mainButton.innerText = buttonText;
  196. mainButton.classList.add(MAIN_BUTTON_CLASS);
  197. mainButton.style.width = '85%';
  198. return mainButton;
  199. };
  200.  
  201. const createSettingButton = () => {
  202. const settingButton = document.createElement('button');
  203. settingButton.innerText = '⚙️';
  204. settingButton.classList.add(MAIN_BUTTON_CLASS);
  205. settingButton.style.width = '15%';
  206. settingButton.id = 'settingButton';
  207. return settingButton;
  208. };
  209.  
  210. const createButtonContainer = () => {
  211. const buttonContainer = document.createElement('div');
  212. buttonContainer.classList.add(CONTAINER_CLASS);
  213.  
  214. const mainButton = createMainButton();
  215. const settingButton = createSettingButton()
  216.  
  217. buttonContainer.appendChild(settingButton);
  218. buttonContainer.appendChild(mainButton);
  219. return buttonContainer;
  220. };
  221.  
  222.  
  223. const style = document.createElement('style');
  224. style.type = 'text/css';
  225. let cssText = '';
  226. for (let selector in buttonStyles) {
  227. cssText += selector + ' {\n';
  228. for (let property in buttonStyles[selector]) {
  229. cssText += ` ${property}: ${buttonStyles[selector][property]};\n`;
  230. }
  231. cssText += '}\n';
  232. }
  233. style.innerHTML = cssText;
  234.  
  235. const assButton = createButtonContainer()
  236. const menu = createMenu(containerNode);
  237.  
  238. // 滑鼠移入按鈕時,顯示選單
  239. assButton.addEventListener('mouseenter', () => {
  240. menu.style.display = 'block';
  241. });
  242.  
  243. // 用 buttonWrapper 把 button 跟 menu 包起來,才能達到 hover 效果
  244. const buttonWrapper = document.createElement('div');
  245. buttonWrapper.style.width = `${containerNode.offsetWidth}px`;
  246. buttonWrapper.appendChild(assButton);
  247. buttonWrapper.appendChild(menu);
  248.  
  249. containerNode.appendChild(buttonWrapper);
  250. containerNode.appendChild(style);
  251.  
  252. // 滑鼠移出 buttonWrapper 時,隱藏選單
  253. buttonWrapper.addEventListener('mouseleave', () => {
  254. menu.style.display = 'none';
  255. });
  256. console.log('已新增按鈕');
  257. };
  258. function showPopup() {
  259. // 找到 settingButton 元素
  260. const settingButton = document.getElementById('settingButton');
  261.  
  262. // 當點擊 settingButton 時觸發事件
  263. settingButton.addEventListener('click', () => {
  264. // 創建彈出視窗
  265. const popup = document.createElement('div');
  266. popup.style.position = 'fixed';
  267. popup.style.top = '50%';
  268. popup.style.left = '50%';
  269. popup.style.transform = 'translate(-50%, -50%)';
  270. popup.style.background = '#525467';
  271. popup.style.border = '1px solid black';
  272. popup.style.padding = '30px';
  273. popup.style.width = '80%';
  274. popup.style.maxWidth = '800px';
  275. popup.style.height = '60%';
  276. popup.style.maxHeight = '1200px';
  277.  
  278. popup.style.zIndex = '9999';
  279.  
  280. // 創建新增按鈕
  281. const addButton = document.createElement('button');
  282. addButton.textContent = '新增(add)';
  283. addButton.style.margin = '10px';
  284. addButton.style.border = '2px solid #ffffff';
  285. addButton.addEventListener('click', () => {
  286. // 新增一個 item
  287. const newItem = {
  288. name: '',
  289. position: '',
  290. content: ''
  291. };
  292. customize.push(newItem);
  293. renderTable();
  294. });
  295. popup.appendChild(addButton);
  296.  
  297. // 創建編輯按鈕
  298. const editButton = document.createElement('button');
  299. editButton.textContent = '編輯(edit)';
  300. editButton.style.margin = '10px';
  301. editButton.style.border = '2px solid #ffffff';
  302. editButton.addEventListener('click', () => {
  303. // 編輯一個 item
  304. const index = prompt('請輸入要編輯的編號(edit index)');
  305. if (index && index >= 1 && index <= customize.length) {
  306. const item = customize[index - 1];
  307.  
  308. // 編輯 name
  309. const newName = prompt('請輸入新的 name', item.name);
  310. if (newName !== null) {
  311. item.name = newName;
  312. }
  313.  
  314. // 編輯 position
  315. do {
  316. newPosition = prompt('請輸入新的 position (只能輸入 start 或 end)', item.position);
  317. } while (newPosition !== null && newPosition !== 'start' && newPosition !== 'end');
  318. if (newPosition !== null) {
  319. item.position = newPosition;
  320. }
  321.  
  322. // 編輯 position
  323. do {
  324. newAutoEnter = prompt('請輸入新的 AutoEnter (只能輸入 y 或 n)', item.autoEnter ? 'y':'n');
  325. } while (newAutoEnter !== null && newAutoEnter !== 'y' && newAutoEnter !== 'n');
  326. if (newAutoEnter !== null) {
  327. if (newAutoEnter === 'y') {
  328. item.autoEnter = true;
  329. } else {
  330. item.autoEnter = false;
  331. }
  332. }
  333.  
  334. // 編輯 content
  335. // const textarea = document.createElement('textarea');
  336. // textarea.value = item.content;
  337. // textarea.style.width = '100%';
  338. // textarea.style.height = '100px';
  339. const newContent = prompt('請輸入新的 content', item.content);
  340. if (newContent !== null) {
  341. item.content = newContent;
  342. }
  343.  
  344. // 重新渲染表格
  345. renderTable();
  346. } else {
  347. alert('輸入的編號不合法');
  348. }
  349. });
  350. popup.appendChild(editButton);
  351.  
  352. // 創建刪除按鈕
  353. const deleteButton = document.createElement('button');
  354. deleteButton.textContent = '刪除(delete)';
  355. deleteButton.style.margin = '10px';
  356. deleteButton.style.border = '2px solid #ffffff';
  357. deleteButton.addEventListener('click', () => {
  358. // 刪除一個 item
  359. const index = prompt('請輸入要刪除的編號(delete index)');
  360. if (index && index >= 1 && index <= customize.length) {
  361. customize.splice(index - 1, 1);
  362. renderTable();
  363. } else {
  364. alert('輸入的編號不合法 (invalid index)');
  365. }
  366. });
  367. popup.appendChild(deleteButton);
  368.  
  369. // 創建關閉按鈕
  370. const closeButton = document.createElement('button');
  371. closeButton.textContent = '儲存並離開(save&exit)';
  372. closeButton.style.position = 'absolute';
  373. closeButton.style.top = '5px';
  374. closeButton.style.right = '5px';
  375. closeButton.addEventListener('click', () => {
  376. console.log(customize);
  377. // 儲存修改後的 customize 資料
  378. GM_setValue('customizeData', customize);
  379.  
  380. // // 重寫一次 helper_menu
  381. // const helper_menu = document.getElementById('helper_menu');
  382. // const menu = createMenu(helper_menu);
  383. // helper_menu.replaceWith(menu);
  384.  
  385. // 上面的做不出來
  386. // 所以只好重新整理頁面
  387. location.reload();
  388.  
  389. document.body.removeChild(popup);
  390. });
  391. popup.appendChild(closeButton);
  392.  
  393. // 創建表格
  394. const table = document.createElement('table');
  395. popup.appendChild(table);
  396.  
  397. // 創建表頭
  398. const thead = document.createElement('thead');
  399. const tr = document.createElement('tr');
  400. const th1 = document.createElement('th');
  401. const th2 = document.createElement('th');
  402. const th3 = document.createElement('th');
  403. const th4 = document.createElement('th');
  404. const th5 = document.createElement('th');
  405. th1.textContent = '編號(index)';
  406. th2.textContent = '名稱(name)';
  407. th3.textContent = '位置(position)';
  408. th4.textContent = '自動輸入(autoEnter)?';
  409. th5.textContent = '內容(content)';
  410. tr.appendChild(th1);
  411. tr.appendChild(th2);
  412. tr.appendChild(th3);
  413. tr.appendChild(th4);
  414. tr.appendChild(th5);
  415. thead.appendChild(tr);
  416. table.appendChild(thead);
  417.  
  418. // 創建表身
  419. const tbody = document.createElement('tbody');
  420. table.appendChild(tbody);
  421.  
  422. // 渲染表格
  423. function renderTable() {
  424. // 先清空表格內容
  425. tbody.innerHTML = '';
  426.  
  427. // 重新渲染表格
  428. customize.forEach((item, index) => {
  429. const tr = document.createElement('tr');
  430. const td1 = document.createElement('td');
  431. const td2 = document.createElement('td');
  432. const td3 = document.createElement('td');
  433. const td4 = document.createElement('td');
  434. const td5 = document.createElement('td');
  435. td1.textContent = index + 1;
  436. td2.textContent = item.name;
  437. td3.textContent = item.position;
  438. td4.textContent = item.autoEnter;
  439. td5.textContent = item.content;
  440. tr.appendChild(td1);
  441. tr.appendChild(td2);
  442. tr.appendChild(td3);
  443. tr.appendChild(td4);
  444. tr.appendChild(td5);
  445. tbody.appendChild(tr);
  446. });
  447. }
  448.  
  449. // 渲染初始表格
  450. renderTable();
  451.  
  452. // 點擊彈窗外的地方關閉彈窗
  453. popup.addEventListener('click', (event) => {
  454. if (event.target === popup) {
  455. document.body.removeChild(popup);
  456. }
  457. });
  458.  
  459. // 將彈出視窗加入頁面中
  460. document.body.appendChild(popup);
  461. });
  462. }
  463.  
  464.  
  465. // // 失敗的監控(((
  466. // // 現在只能用 setInterval + setTimeout 去添加按鈕
  467. // const observeElementChanges = (trigger, selector, callback) => {
  468. // const triggerNode = document.querySelector(trigger);
  469. // const aimsNode = document.querySelector(selector);
  470.  
  471. // if (!triggerNode) {
  472. // console.warn(`目標元素 ${selector} 不存在,無法監控`);
  473. // return;
  474. // }
  475.  
  476. // const observer = new MutationObserver((mutationsList) => {
  477. // for (const mutation of mutationsList) {
  478. // if (mutation.type === 'childList') {
  479. // callback(mutation, aimsNode);
  480. // }
  481. // }
  482. // });
  483. // console.log(`目標元素 ${selector} 存在,監控中`);
  484. // observer.observe(triggerNode, { childList: true });
  485. // };
  486.  
  487. const main = () => {
  488. const container = document.getElementById('helper_menu');
  489. let GM_customize = GM_getValue('customizeData', customize); // 讀取 customize 數據
  490. if (GM_customize) {
  491. customize = GM_customize;
  492. } else {
  493. customize = init_customize;
  494. GM_setValue('customizeData', customize);
  495. }
  496.  
  497. //找不到就新增
  498. if (!container) {
  499. const aimsNode = document.querySelector(NAV_MENU);
  500. // 新增一個容器
  501. const container = document.createElement('div');
  502. container.classList.add(CONTAINER_CLASS);
  503. container.id = 'helper_menu';
  504. container.style.width = `${aimsNode.offsetWidth}px`; // 設定 container 寬度為父元素寬度
  505.  
  506. // 將容器元素插入到目標元素後面
  507. aimsNode.parentNode.insertBefore(container, aimsNode.nextSibling);
  508.  
  509. // 新增一個按鈕元素
  510. addButton(container, customize, HELPER_MENU_TEST);
  511.  
  512. showPopup()
  513. }
  514. };
  515.  
  516. const checkUrl = () => {
  517. if (window.location.href !== checkUrl.currentUrl) {
  518. console.log(`網址變更為 ${window.location.href}`);
  519. checkUrl.currentUrl = window.location.href;
  520. setTimeout(main, 100);
  521. }
  522. };
  523.  
  524. (() => {
  525. // 妥協的結果,用 setInterval + setTimeout 去添加按鈕
  526. // 之後一定要改好!!!
  527. checkUrl.currentUrl = window.location.href;
  528. setInterval(checkUrl, 1000);
  529. setTimeout(main, 2000);
  530. // GM_deleteValue('customizeData');
  531. })();