ChatGPT-input-helper

Help organize commonly used spells quickly

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

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