- // ==UserScript==
- // @name ChatGPT helper
- // @namespace we684123
- // @version 0.0.1
- // @author we684123
- // @description 幫助快速組織常用咒文
- // @license MIT
- // @icon https://chat.openai.com/favicon.ico
- // @match https://chat.openai.com/chat
- // @match https://chat.openai.com/chat/*
- // @grant GM_getValue
- // @grant GM_setValue
- // @grant GM_deleteValue
- // @run-at document-end
- // ==/UserScript==
-
-
- // 定義常用咒文
- let customize
-
- // 定義初始化咒文
- let init_customize = [
- {
- name: '繁體中文初始化', // 按鈕的顯示名稱
- position: 'start', // start = 最前面 , end = 最後面
- autoEnter: true, // 是否自動按下 Enter
- content: [ // 要被放入輸入框的文字
- `以下問答請使用繁體中文,並使用台灣用語。\n`,
- `代碼請改用程式碼,"返回"改為"回傳","水平"改為"水準","質量"改為"品質",`,
- `"示例"改為"範例","博客"改為"部落格","數據庫"改為"資料庫",`,
- `"打印"改為"印出","默認"改為"預設","變量"改"變數"。\n`,
- `以上為台灣用語的一部分。`,
- ].join("")
- }, {
- name: '解釋程式碼', // 按鈕的顯示名稱
- position: 'start', // start = 最前面 , end = 最後面
- autoEnter: false, // 是否自動按下 Enter
- content: [ // 要被放入輸入框的文字
- `請解釋這段程式碼\n\n`,
- ].join("")
- }, {
- name: '改善程式碼', // 按鈕的顯示名稱
- position: 'start', // start = 最前面 , end = 最後面
- autoEnter: false, // 是否自動按下 Enter
- content: [ // 要被放入輸入框的文字
- `請改善這段程式碼\n\n`,
- ].join("")
- }, {
- name: '請繼續', // 按鈕的顯示名稱
- position: 'start', // start = 最前面 , end = 最後面
- autoEnter: false, // 是否自動按下 Enter
- content: [ // 要被放入輸入框的文字
- `請繼續`,
- ].join("")
- }
- ];
-
- // 定義雜七雜八
- const HELPER_MENU_TEST = 'input helper'; // 按鈕文字
- const CONTAINER_CLASS = 'helper_textcontainer'; // 按鈕用容器
- const MAIN_BUTTON_CLASS = 'main_button'; // 選單按鈕
- const SETTING_BUTTON_CLASS = 'setting_button'; // 控制按鈕
- const MENU_CLASS = 'main_menu'; // 選單
-
- const NAV_SELECTOR = 'nav.flex.h-full.flex-1.flex-col.space-y-1.p-2'; // 左邊選單的定位(整體)
- const NAV_MENU = 'nav > div.overflow-y-auto'; // 左邊選單的定位(上層)
- const TEXT_INPUTBOX_POSITION = 'textarea.m-0'; // 輸入框的定位
- const SUBMIT_BUTTON_POSITION = 'button.absolute'; // 送出按鈕的定位
- const INPUT_EVENT = new Event('input', { bubbles: true }); // 模擬輸入於輸入框的事件
-
-
- // 準備 function
- const insertCustomize = (customize, name) => {
- // customize = 設定的object
- // name = 觸發按鈕的名稱
-
- console.log('insertCustomize 開始執行');
- const textInputbox = document.querySelector(TEXT_INPUTBOX_POSITION);
- const submitButton = document.querySelector(SUBMIT_BUTTON_POSITION);
- const item = customize.find(i => i.name === name);
-
- if (item) {
- console.log(`已找到名稱為 ${name} 的元素`);
-
- // start = 最前面 , end = 最後面
- // 有想要出游標定位,之後再說
- // 例如 `請從 "" 繼續輸出` 就希望定在 "" 之間
- if (item.position === 'start') {
- textInputbox.value = item.content + textInputbox.value;
- } else {
- textInputbox.value += item.content;
- }
-
- // 模擬,讓對話框可以自動更新高度
- textInputbox.dispatchEvent(INPUT_EVENT);
- textInputbox.focus();
-
- console.log(`textInputbox.value =\n${textInputbox.value}`);
-
- // 如果需要,自動按下 Enter
- if (item.autoEnter) {
- console.log('autoEnter (O)');
- submitButton.click();
- }
-
- console.log('====== insertCustomize 結束 ======');
- } else {
- console.error(`找不到名稱為 ${name} 的元素`);
- }
- };
-
-
- const buttonStyles = {
- [`.${CONTAINER_CLASS}`]: {
- position: 'relative',
- display: 'flex',
- alignItems: 'center',
- justifyContent: 'center',
- borderBottom: '1px solid rgba(255, 255, 255, 0.2)',
- border: '1px solid #fff',
- borderRadius: '5px',
- width: '100%',
- boxSizing: 'border-box',
- },
- [`.${MAIN_BUTTON_CLASS}`]: {
- padding: '8px 12px',
- fontSize: '14px',
- color: '#fff',
- border: '1px solid #fff',
- backgroundColor: '#525252',
- borderRadius: '5px',
- cursor: 'pointer',
- width: '85%',
- boxSizing: 'border-box',
- margin: '0 auto', // 水平置中
- },
- [`.${SETTING_BUTTON_CLASS}`]: {
- padding: '8px 12px',
- fontSize: '14px',
- color: '#fff',
- backgroundColor: '#525252',
- border: 'none',
- borderRadius: '5px',
- cursor: 'pointer',
- width: '15%',
- boxSizing: 'border-box',
- },
- [`.${MENU_CLASS}`]: {
- display: 'none',
- position: 'absolute',
- left: '100%',
- top: 0,
- zIndex: 1,
- width: '100%',
- backgroundColor: '#2b2c2f', // 新增背景色設定
- border: '1px solid #fff', // 新增邊框設定
- borderRadius: '15px', // 新增圓角設定
- },
- [`.${MENU_CLASS} button`]: {
- display: 'block',
- width: '100%',
- height: '100%',
- padding: '8px 12px',
- fontSize: '14px',
- color: '#fff',
- backgroundColor: '#2b2c2f',
- border: '1px solid #fff',
- borderRadius: '5px', // 圓角
- cursor: 'pointer',
- },
- };
-
-
-
- const createMenu = (containerNode) => {
- const menu = document.createElement('div');
- menu.id = 'helper_menu';
- menu.classList.add(MENU_CLASS);
- menu.style.display = 'none';
- menu.style.width = `${containerNode.offsetWidth}px`
-
- customize.forEach(element => {
- const menuItem = createMenuItem(element);
- menu.appendChild(menuItem);
- });
-
- return menu;
- };
-
- const createMenuItem = (element) => {
- const menuItem = document.createElement('button');
- menuItem.innerText = element.name;
- menuItem.id = element.name;
- menuItem.addEventListener('click', (event) => {
- console.log('按鈕資訊:', event.target);
- insertCustomize(customize, event.target.id);
- });
-
- return menuItem;
- };
- const addButton = (containerNode, customize, buttonText = 'Click Me') => {
- const createMainButton = () => {
- const mainButton = document.createElement('button');
- mainButton.innerText = buttonText;
- mainButton.classList.add(MAIN_BUTTON_CLASS);
- mainButton.style.width = '85%';
- return mainButton;
- };
-
- const createSettingButton = () => {
- const settingButton = document.createElement('button');
- settingButton.innerText = '⚙️';
- settingButton.classList.add(MAIN_BUTTON_CLASS);
- settingButton.style.width = '15%';
- settingButton.id = 'settingButton';
- return settingButton;
- };
-
- const createButtonContainer = () => {
- const buttonContainer = document.createElement('div');
- buttonContainer.classList.add(CONTAINER_CLASS);
-
- const mainButton = createMainButton();
- const settingButton = createSettingButton()
-
- buttonContainer.appendChild(settingButton);
- buttonContainer.appendChild(mainButton);
- return buttonContainer;
- };
-
-
- const style = document.createElement('style');
- style.type = 'text/css';
- let cssText = '';
- for (let selector in buttonStyles) {
- cssText += selector + ' {\n';
- for (let property in buttonStyles[selector]) {
- cssText += ` ${property}: ${buttonStyles[selector][property]};\n`;
- }
- cssText += '}\n';
- }
- style.innerHTML = cssText;
-
- const assButton = createButtonContainer()
- const menu = createMenu(containerNode);
-
- // 滑鼠移入按鈕時,顯示選單
- assButton.addEventListener('mouseenter', () => {
- menu.style.display = 'block';
- });
-
- // 用 buttonWrapper 把 button 跟 menu 包起來,才能達到 hover 效果
- const buttonWrapper = document.createElement('div');
- buttonWrapper.style.width = `${containerNode.offsetWidth}px`;
- buttonWrapper.appendChild(assButton);
- buttonWrapper.appendChild(menu);
-
- containerNode.appendChild(buttonWrapper);
- containerNode.appendChild(style);
-
- // 滑鼠移出 buttonWrapper 時,隱藏選單
- buttonWrapper.addEventListener('mouseleave', () => {
- menu.style.display = 'none';
- });
- console.log('已新增按鈕');
- };
- function showPopup() {
- // 找到 settingButton 元素
- const settingButton = document.getElementById('settingButton');
-
- // 當點擊 settingButton 時觸發事件
- settingButton.addEventListener('click', () => {
- // 創建彈出視窗
- const popup = document.createElement('div');
- popup.style.position = 'fixed';
- popup.style.top = '50%';
- popup.style.left = '50%';
- popup.style.transform = 'translate(-50%, -50%)';
- popup.style.background = '#525467';
- popup.style.border = '1px solid black';
- popup.style.padding = '30px';
- popup.style.width = '80%';
- popup.style.maxWidth = '800px';
- popup.style.height = '60%';
- popup.style.maxHeight = '1200px';
-
- popup.style.zIndex = '9999';
-
- // 創建新增按鈕
- const addButton = document.createElement('button');
- addButton.textContent = '新增(add)';
- addButton.style.margin = '10px';
- addButton.style.border = '2px solid #ffffff';
- addButton.addEventListener('click', () => {
- // 新增一個 item
- const newItem = {
- name: '',
- position: '',
- content: ''
- };
- customize.push(newItem);
- renderTable();
- });
- popup.appendChild(addButton);
-
- // 創建編輯按鈕
- const editButton = document.createElement('button');
- editButton.textContent = '編輯(edit)';
- editButton.style.margin = '10px';
- editButton.style.border = '2px solid #ffffff';
- editButton.addEventListener('click', () => {
- // 編輯一個 item
- const index = prompt('請輸入要編輯的編號(edit index)');
- if (index && index >= 1 && index <= customize.length) {
- const item = customize[index - 1];
-
- // 編輯 name
- const newName = prompt('請輸入新的 name', item.name);
- if (newName !== null) {
- item.name = newName;
- }
-
- // 編輯 position
- do {
- newPosition = prompt('請輸入新的 position (只能輸入 start 或 end)', item.position);
- } while (newPosition !== null && newPosition !== 'start' && newPosition !== 'end');
- if (newPosition !== null) {
- item.position = newPosition;
- }
-
- // 編輯 position
- do {
- newAutoEnter = prompt('請輸入新的 AutoEnter (只能輸入 y 或 n)', item.autoEnter);
- } while (newAutoEnter !== null && newAutoEnter !== 'y' && newAutoEnter !== 'n');
- if (newAutoEnter !== null) {
- if (newAutoEnter === 'y') {
- item.autoEnter = true;
- } else {
- item.autoEnter = false;
- }
- }
-
- // 編輯 content
- // const textarea = document.createElement('textarea');
- // textarea.value = item.content;
- // textarea.style.width = '100%';
- // textarea.style.height = '100px';
- const newContent = prompt('請輸入新的 content', item.content);
- if (newContent !== null) {
- item.content = newContent;
- }
-
- // 重新渲染表格
- renderTable();
- } else {
- alert('輸入的編號不合法');
- }
- });
- popup.appendChild(editButton);
-
- // 創建刪除按鈕
- const deleteButton = document.createElement('button');
- deleteButton.textContent = '刪除(delete)';
- deleteButton.style.margin = '10px';
- deleteButton.style.border = '2px solid #ffffff';
- deleteButton.addEventListener('click', () => {
- // 刪除一個 item
- const index = prompt('請輸入要刪除的編號(delete index)');
- if (index && index >= 1 && index <= customize.length) {
- customize.splice(index - 1, 1);
- renderTable();
- } else {
- alert('輸入的編號不合法 (invalid index)');
- }
- });
- popup.appendChild(deleteButton);
-
- // 創建關閉按鈕
- const closeButton = document.createElement('button');
- closeButton.textContent = '儲存並離開(save&exit)';
- closeButton.style.position = 'absolute';
- closeButton.style.top = '5px';
- closeButton.style.right = '5px';
- closeButton.addEventListener('click', () => {
- console.log(customize);
- // 儲存修改後的 customize 資料
- GM_setValue('customizeData', customize);
-
- // // 重寫一次 helper_menu
- // const helper_menu = document.getElementById('helper_menu');
- // const menu = createMenu(helper_menu);
- // helper_menu.replaceWith(menu);
-
- // 上面的做不出來
- // 所以只好重新整理頁面
- location.reload();
-
- document.body.removeChild(popup);
- });
- popup.appendChild(closeButton);
-
- // 創建表格
- const table = document.createElement('table');
- popup.appendChild(table);
-
- // 創建表頭
- const thead = document.createElement('thead');
- const tr = document.createElement('tr');
- const th1 = document.createElement('th');
- const th2 = document.createElement('th');
- const th3 = document.createElement('th');
- const th4 = document.createElement('th');
- const th5 = document.createElement('th');
- th1.textContent = '編號(index)';
- th2.textContent = '名稱(name)';
- th3.textContent = '位置(position)';
- th4.textContent = '自動輸入(autoEnter)?';
- th5.textContent = '內容(content)';
- tr.appendChild(th1);
- tr.appendChild(th2);
- tr.appendChild(th3);
- tr.appendChild(th4);
- tr.appendChild(th5);
- thead.appendChild(tr);
- table.appendChild(thead);
-
- // 創建表身
- const tbody = document.createElement('tbody');
- table.appendChild(tbody);
-
- // 渲染表格
- function renderTable() {
- // 先清空表格內容
- tbody.innerHTML = '';
-
- // 重新渲染表格
- customize.forEach((item, index) => {
- const tr = document.createElement('tr');
- const td1 = document.createElement('td');
- const td2 = document.createElement('td');
- const td3 = document.createElement('td');
- const td4 = document.createElement('td');
- const td5 = document.createElement('td');
- td1.textContent = index + 1;
- td2.textContent = item.name;
- td3.textContent = item.position;
- td4.textContent = item.autoEnter;
- td5.textContent = item.content;
- tr.appendChild(td1);
- tr.appendChild(td2);
- tr.appendChild(td3);
- tr.appendChild(td4);
- tr.appendChild(td5);
- tbody.appendChild(tr);
- });
- }
-
- // 渲染初始表格
- renderTable();
-
- // 點擊彈窗外的地方關閉彈窗
- popup.addEventListener('click', (event) => {
- if (event.target === popup) {
- document.body.removeChild(popup);
- }
- });
-
- // 將彈出視窗加入頁面中
- document.body.appendChild(popup);
- });
- }
-
-
- // // 失敗的監控(((
- // // 現在只能用 setInterval + setTimeout 去添加按鈕
- // const observeElementChanges = (trigger, selector, callback) => {
- // const triggerNode = document.querySelector(trigger);
- // const aimsNode = document.querySelector(selector);
-
- // if (!triggerNode) {
- // console.warn(`目標元素 ${selector} 不存在,無法監控`);
- // return;
- // }
-
- // const observer = new MutationObserver((mutationsList) => {
- // for (const mutation of mutationsList) {
- // if (mutation.type === 'childList') {
- // callback(mutation, aimsNode);
- // }
- // }
- // });
- // console.log(`目標元素 ${selector} 存在,監控中`);
- // observer.observe(triggerNode, { childList: true });
- // };
-
- const main = () => {
- const container = document.getElementById('helper_menu');
- let GM_customize = GM_getValue('customizeData', customize); // 讀取 customize 數據
- if (GM_customize) {
- customize = GM_customize;
- } else {
- customize = init_customize;
- GM_setValue('customizeData', customize);
- }
-
- //找不到就新增
- if (!container) {
- const aimsNode = document.querySelector(NAV_MENU);
- // 新增一個容器
- const container = document.createElement('div');
- container.classList.add(CONTAINER_CLASS);
- container.id = 'helper_menu';
- container.style.width = `${aimsNode.offsetWidth}px`; // 設定 container 寬度為父元素寬度
-
- // 將容器元素插入到目標元素後面
- aimsNode.parentNode.insertBefore(container, aimsNode.nextSibling);
-
- // 新增一個按鈕元素
- addButton(container, customize, HELPER_MENU_TEST);
-
- showPopup()
- }
- };
-
- const checkUrl = () => {
- if (window.location.href !== checkUrl.currentUrl) {
- console.log(`網址變更為 ${window.location.href}`);
- checkUrl.currentUrl = window.location.href;
- setTimeout(main, 100);
- }
- };
-
- (() => {
- // 妥協的結果,用 setInterval + setTimeout 去添加按鈕
- // 之後一定要改好!!!
- checkUrl.currentUrl = window.location.href;
- setInterval(checkUrl, 1000);
- setTimeout(main, 2000);
- // GM_deleteValue('customizeData');
- })();