您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Illusion(幻觉)是一个跨平台 Prompts 管理工具,支持在以下平台使用:Google AI Studio, ChatGPT, Claude, Grok 和 DeepSeek
// ==UserScript== // @name Illusion // @icon https://raw.githubusercontent.com/cattail-mutt/Illusion/refs/heads/main/image/icons/illusion.png // @namespace https://github.com/cattail-mutt // @version 1.5 // @description Illusion(幻觉)是一个跨平台 Prompts 管理工具,支持在以下平台使用:Google AI Studio, ChatGPT, Claude, Grok 和 DeepSeek // @author Mukai // @license MIT // @match https://aistudio.google.com/* // @match https://chatgpt.com/* // @match https://claude.ai/* // @match https://chat.deepseek.com/* // @match https://grok.com/* // @grant GM_setValue // @grant GM_getValue // @grant GM_getResourceText // @resource PROMPTS https://raw.githubusercontent.com/cattail-mutt/Illusion/refs/heads/main/prompt/prompts.json // @resource THEMES https://raw.githubusercontent.com/cattail-mutt/Illusion/refs/heads/main/style/themes.json // @resource CSS https://raw.githubusercontent.com/cattail-mutt/Illusion/refs/heads/main/style/illusion.css // @homepage https://greasyfork.dpdns.org/zh-CN/scripts/527451-%E5%B9%BB%E8%A7%89-illusion // ==/UserScript== (function() { 'use strict'; const debug = { enabled: true, log: (...args) => debug.enabled && console.log('> Illusion 日志:', ...args), error: (...args) => console.error('> Illusion 错误:', ...args) }; let modalRef = null; let overlayRef = null; let savedPrompts = {}; const initialPrompts = JSON.parse(GM_getResourceText('PROMPTS')); const promptsObject = Object.fromEntries( (initialPrompts || []) .filter(item => item?.id && item?.value) .map(item => [item.id, item.value]) ); console.log('PROMPTS解析结果:', promptsObject); const THEMECONFIG = JSON.parse(GM_getResourceText('THEMES')); debug.log('THEMES解析结果:', THEMECONFIG); function dispatchEvents(element, events) { events.forEach(eventName => { const event = eventName === 'input' ? new InputEvent(eventName, { bubbles: true }) : new Event(eventName, { bubbles: true }); element.dispatchEvent(event); }); } function createParagraph(line) { const p = document.createElement('p'); if (line.trim()) { p.textContent = line; } else { p.innerHTML = '<br>'; } return p; } const updateProseMirror = (editor, prompt) => { // ChatGPT 和 Claude 均使用了 ProseMirror 库构建富文本编辑器 const paragraphs = Array.from(editor.querySelectorAll('p')); let currentContent = ''; paragraphs.forEach(p => { const text = p.textContent.trim(); if (text) { currentContent += text + '\n'; } else { currentContent += '\n'; } }); let newContent = currentContent.trim(); if (newContent) { newContent += '\n'; } editor.innerHTML = ''; if (newContent) { newContent.split('\n').forEach(line => { editor.appendChild(createParagraph(line)); }); } const lines = prompt.split('\n'); lines.forEach((line, index) => { editor.appendChild(createParagraph(line)); if (index < lines.length - 1 && !line.trim()) { const brP = document.createElement('p'); brP.innerHTML = '<br>'; editor.appendChild(brP); } }); dispatchEvents(editor, ['input', 'change']); }; const updateTextArea = async (textarea, prompt) => { // Gemini 和 DeepSeek 使用的均是纯文本输入框 <textarea> const currentContent = textarea.value; const newContent = currentContent === '' ? prompt : currentContent + "\n" + prompt; const setter = Object.getOwnPropertyDescriptor( window.HTMLTextAreaElement.prototype, "value" ).set; setter.call(textarea, newContent); dispatchEvents(textarea, ['focus', 'input', 'change']); }; const CONFIG = { maxRetries: 3, retryDelay: 1000, initTimeout: 10000, sync: { enabled: true, // 是否同步仓库中的 prompts.yaml blacklist: ['undesired_prompt,e.g. dev', 'undesired_prompt,e.g. graphviz'] // 同步黑名单,其中的键名对应的提示词将不会被同步 }, sites: { CHATGPT: { id: 'chatgpt', icon: 'https://raw.githubusercontent.com/cattail-mutt/Illusion/refs/heads/main/image/icons/chatgpt.svg', buttonSize: '48px', selector: 'div.ProseMirror[contenteditable=true]', setPrompt: updateProseMirror }, CLAUDE: { // CSP 限制:用 svg 塞图标 id: 'claude', icon: `<svg xmlns="http://www.w3.org/2000/svg" style="flex:none;line-height:1" viewBox="0 0 24 24"><title>Claude</title><path d="M4.709 15.955l4.72-2.647.08-.23-.08-.128H9.2l-.79-.048-2.698-.073-2.339-.097-2.266-.122-.571-.121L0 11.784l.055-.352.48-.321.686.06 1.52.103 2.278.158 1.652.097 2.449.255h.389l.055-.157-.134-.098-.103-.097-2.358-1.596-2.552-1.688-1.336-.972-.724-.491-.364-.462-.158-1.008.656-.722.881.06.225.061.893.686 1.908 1.476 2.491 1.833.365.304.145-.103.019-.073-.164-.274-1.355-2.446-1.446-2.49-.644-1.032-.17-.619a2.97 2.97 0 01-.104-.729L6.283.134 6.696 0l.996.134.42.364.62 1.414 1.002 2.229 1.555 3.03.456.898.243.832.091.255h.158V9.01l.128-1.706.237-2.095.23-2.695.08-.76.376-.91.747-.492.584.28.48.685-.067.444-.286 1.851-.559 2.903-.364 1.942h.212l.243-.242.985-1.306 1.652-2.064.73-.82.85-.904.547-.431h1.033l.76 1.129-.34 1.166-1.064 1.347-.881 1.142-1.264 1.7-.79 1.36.073.11.188-.02 2.856-.606 1.543-.28 1.841-.315.833.388.091.395-.328.807-1.969.486-2.309.462-3.439.813-.042.03.049.061 1.549.146.662.036h1.622l3.02.225.79.522.474.638-.079.485-1.215.62-1.64-.389-3.829-.91-1.312-.329h-.182v.11l1.093 1.068 2.006 1.81 2.509 2.33.127.578-.322.455-.34-.049-2.205-1.657-.851-.747-1.926-1.62h-.128v.17l.444.649 2.345 3.521.122 1.08-.17.353-.608.213-.668-.122-1.374-1.925-1.415-2.167-1.143-1.943-.14.08-.674 7.254-.316.37-.729.28-.607-.461-.322-.747.322-1.476.389-1.924.315-1.53.286-1.9.17-.632-.012-.042-.14.018-1.434 1.967-2.18 2.945-1.726 1.845-.414.164-.717-.37.067-.662.401-.589 2.388-3.036 1.44-1.882.93-1.086-.006-.158h-.055L4.132 18.56l-1.13.146-.487-.456.061-.746.231-.243 1.908-1.312-.006.006z" fill="#D97757" fill-rule="nonzero"/></svg>`, buttonSize: '48px', selector: 'div.ProseMirror[contenteditable=true]', setPrompt: updateProseMirror }, DEEPSEEK: { id: 'deepseek', icon: 'https://raw.githubusercontent.com/cattail-mutt/Illusion/refs/heads/main/image/icons/deepseek.svg', buttonSize: '48px', selector: 'textarea[id="chat-input"]', setPrompt: updateTextArea }, GEMINI: { id: 'gemini', icon: 'https://www.gstatic.com/lamda/images/gemini_sparkle_v002_d4735304ff6292a690345.svg', buttonSize: '48px', selector: 'ms-autosize-textarea textarea', setPrompt: updateTextArea }, GROK: { // CSP 限制:用 svg 塞图标 id: 'grok', icon: '<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" fill-rule="evenodd" viewBox="0 0 24 24"><title>Grok</title><path d="M9.27 15.29l7.978-5.897c.391-.29.95-.177 1.137.272.98 2.369.542 5.215-1.41 7.169-1.951 1.954-4.667 2.382-7.149 1.406l-2.711 1.257c3.889 2.661 8.611 2.003 11.562-.953 2.341-2.344 3.066-5.539 2.388-8.42l.006.007c-.983-4.232.242-5.924 2.75-9.383.06-.082.12-.164.179-.248l-3.301 3.305v-.01L9.267 15.292M7.623 16.723c-2.792-2.67-2.31-6.801.071-9.184 1.761-1.763 4.647-2.483 7.166-1.425l2.705-1.25a7.808 7.808 0 00-1.829-1A8.975 8.975 0 005.984 5.83c-2.533 2.536-3.33 6.436-1.962 9.764 1.022 2.487-.653 4.246-2.34 6.022-.599.63-1.199 1.259-1.682 1.925l7.62-6.815"/></svg>', buttonSize: '48px', selector: 'textarea', setPrompt: updateTextArea } } }; function loadExternalCSS() { const style = document.createElement('style'); style.textContent = GM_getResourceText('CSS'); document.head.appendChild(style); } function loadsiteTheme() { const currentSite = getCurrentSite(); const theme = THEMECONFIG[currentSite]; const config = Object.values(CONFIG.sites).find(s => s.id === currentSite); const root = document.documentElement; root.style.setProperty('--secondary-bg', theme.secondary); root.style.setProperty('--text-color', theme.text); root.style.setProperty('--border-color', theme.border); root.style.setProperty('--button-bg', theme.button.bg); root.style.setProperty('--button-hover', theme.button.hover); root.style.setProperty('--button-size', config.buttonSize); root.style.setProperty('--panel-bg', theme.panel.bg); root.style.setProperty('--panel-button-bg', theme.panel.buttonBg); root.style.setProperty('--panel-button-hover', theme.panel.buttonHover); } function waitForElement(selector, maxTimeout = CONFIG.initTimeout) { return new Promise((resolve, reject) => { const element = document.querySelector(selector); if(element) { return resolve(element); } let timeout; const observer = new MutationObserver(() => { const el = document.querySelector(selector); if (el) { observer.disconnect(); clearTimeout(timeout); resolve(el); } }); timeout = setTimeout(() => { observer.disconnect(); reject(new Error(`超时:使用选择器 ${selector} 寻找元素`)); }, maxTimeout); observer.observe(document.body, { childList: true, subtree: true }); }); } function getCurrentSite() { const url = window.location.href; if(url.includes('aistudio.google.com')) return CONFIG.sites.GEMINI.id; if(url.includes('chatgpt.com')) return CONFIG.sites.CHATGPT.id; if(url.includes('claude.ai')) return CONFIG.sites.CLAUDE.id; if(url.includes('chat.deepseek.com')) return CONFIG.sites.DEEPSEEK.id; if(url.includes('grok.com')) return CONFIG.sites.GROK.id; } function createElement(tag, attributes = {}) { const element = document.createElement(tag); Object.entries(attributes).forEach(([key, value]) => { if (key === 'className') { element.className = value; } else if (key === 'textContent') { element.textContent = value; } else if (key === 'style' && typeof value === 'object') { Object.assign(element.style, value); } else if (key === 'onclick') { element.addEventListener('click', value); } else if (key.startsWith('data-')) { element.setAttribute(key, value); } else if (key === 'html') { element.innerHTML = value; } else if (key === 'on' && typeof value === 'object') { Object.entries(value).forEach(([event, handler]) => { element.addEventListener(event, handler); }); } else { element.setAttribute(key, value); } }); return element; } function filterPromptsByBlacklist(prompts) { const blacklist = new Set(CONFIG.sync.blacklist); const filteredPrompts = {}; for (const [id, content] of Object.entries(prompts)) { if (!blacklist.has(id)) { filteredPrompts[id] = content; } else { debug.log(`提示词 "${id}" 在黑名单中,已过滤`); } } return filteredPrompts; } function syncPrompts(storedPrompts, initialPromptsObject) { if (!CONFIG.sync.enabled) { debug.log('提示词同步功能已禁用'); return storedPrompts; } debug.log('提示词库同步中...'); const filteredInitialPrompts = filterPromptsByBlacklist(initialPromptsObject); let hasNewPrompts = false; const syncedPrompts = { ...storedPrompts }; for (const [id, content] of Object.entries(filteredInitialPrompts)) { if (!syncedPrompts[id]) { debug.log(`发现新提示词: "${id}"`); syncedPrompts[id] = content; hasNewPrompts = true; } } if (hasNewPrompts) { debug.log('同步完成,发现并添加了新的提示词'); GM_setValue('prompts', syncedPrompts); } else { debug.log('同步完成,没有发现新的提示词'); } return syncedPrompts; } function loadPrompts() { debug.log('正在载入提示词...'); const storedPrompts = GM_getValue('prompts'); if (!storedPrompts) { const filteredPrompts = filterPromptsByBlacklist(promptsObject); savedPrompts = filteredPrompts; debug.log('未发现 GM 存储中的提示词,将使用初始化时加载的默认提示词:', savedPrompts); GM_setValue('prompts', savedPrompts); } else { debug.log('发现 GM 存储中已有提示词如下', storedPrompts); savedPrompts = syncPrompts(storedPrompts, promptsObject); } return savedPrompts; } function saveNewPrompt(id, content) { savedPrompts[id] = content; GM_setValue('prompts', savedPrompts); updateDatalist(); debug.log('新的提示词已保存:', id); } async function setPromptWithRetry(site, prompt, maxRetries = CONFIG.maxRetries) { return new Promise(async (resolve, reject) => { let attempts = 0; const trySetPrompt = async () => { try { const config = Object.values(CONFIG.sites).find(s => s.id === site); if(!config || !config.setPrompt) { return reject(new Error(`缺少当前站点对应的文本编辑器配置: ${site}`)); } const editor = document.querySelector(config.selector); if(!editor) { throw new Error('在页面上没有找到配置所指定的文本编辑器'); } await config.setPrompt(editor, prompt); resolve(true); } catch (err) { if (attempts < maxRetries) { attempts++; setTimeout(trySetPrompt, CONFIG.retryDelay); } else { reject(err); } } }; trySetPrompt(); }); } function initializeUI() { if (!document.body) { console.error('找不到 <body>'); setTimeout(initializeUI, 100); return; } loadExternalCSS(); loadsiteTheme(); const button = createButton(); const panel = createPanel(); const { modal, overlay } = createModal(); setupEventListeners(button, panel, modal, overlay); } function createButton() { const button = createElement('div', { className: 'illusion-button', 'data-tooltip': 'Illusion', 'data-tooltip-position': 'left', 'aria-label': 'Illusion' }); const currentSite = getCurrentSite(); const config = Object.values(CONFIG.sites).find(s => s.id === currentSite); if(config.icon.startsWith('http')) { const img = createElement('img', { src: config.icon, width: '24', height: '24', style: { pointerEvents: 'none' } }); button.appendChild(img); } else { button.innerHTML = config.icon; const svg = button.querySelector('svg'); if(svg) { svg.style.width = '24px'; svg.style.height = '24px'; svg.style.pointerEvents = 'none'; } } document.body.appendChild(button); makeDraggable(button); return button; } function createPanel() { const panel = createElement('div', { className: 'illusion-panel' }); const title = createElement('div', { className: 'panel-title', textContent: 'Illusion' }); panel.appendChild(title); const inputGroup = createElement('div', { className: 'input-group' }); const input = createElement('input', { className: 'prompt-input', type: 'text', list: 'prompt-options', placeholder: '查找 Prompt' }); const datalist = createElement('datalist', { id: 'prompt-options' }); Object.keys(savedPrompts).forEach(id => { const option = createElement('option', { value: id }); datalist.appendChild(option); }); inputGroup.appendChild(input); inputGroup.appendChild(datalist); panel.appendChild(inputGroup); const buttonGroup = createElement('div', { className: 'button-group' }); const newButton = createElement('button', { className: 'panel-button', textContent: 'New', 'data-action': 'new', onclick: () => { const modal = document.querySelector('.modal'); const overlay = document.querySelector('.modal-overlay'); if (modal && overlay) { showNewPromptModal(modal, overlay); } else { debug.error('找不到模态框/遮罩层'); } } }); buttonGroup.appendChild(newButton); const manageButton = createElement('button', { className: 'panel-button', textContent: 'Manage', 'data-action': 'manage', onclick: () => { const modal = document.querySelector('.modal'); const overlay = document.querySelector('.modal-overlay'); if (modal && overlay) { showManagePromptsModal(modal, overlay); } else { debug.error('找不到模态框/遮罩层'); } } }); buttonGroup.appendChild(manageButton); panel.appendChild(buttonGroup); document.body.appendChild(panel); return panel; } function createModal() { overlayRef = createElement('div', { className: 'modal-overlay' }); document.body.appendChild(overlayRef); modalRef = createElement('div', { className: 'modal', role: 'dialog', 'aria-modal': 'true' }); document.body.appendChild(modalRef); return { modal: modalRef, overlay: overlayRef }; } function createModalFooter(buttonConfigs, modal, overlay) { const footer = createElement('div', { className: 'modal-footer' }); buttonConfigs.forEach(config => { const btn = createElement('button', { className: 'panel-button', textContent: config.text, onclick: () => { config.onClick && config.onClick(modal, overlay); } }); footer.appendChild(btn); }); return footer; } function openModal(modal, overlay, container) { modal.textContent = ''; modal.appendChild(container); modal.classList.add('visible'); overlay.classList.add('visible'); } function hideModal(modal, overlay) { modal.classList.remove('visible'); overlay.classList.remove('visible'); } function getEventPosition(e) { if (e.touches && e.touches.length) { return { x: e.touches[0].clientX, y: e.touches[0].clientY }; } return { x: e.clientX, y: e.clientY }; } function makeDraggable(button) { let isDragging = false; let startX, startY; let initialX, initialY; let lastValidX, lastValidY; let dragThrottle; function setButtonPosition(x, y) { button.style.left = x + 'px'; button.style.top = y + 'px'; GM_setValue('buttonPosition', { x, y }); } function dragStart(e) { const pos = getEventPosition(e); startX = pos.x; startY = pos.y; const rect = button.getBoundingClientRect(); initialX = rect.left; initialY = rect.top; isDragging = true; button.classList.remove('docked'); button.classList.add('dragging'); } function dragEnd() { if (!isDragging) return; isDragging = false; button.classList.remove('dragging'); const rect = button.getBoundingClientRect(); const viewportWidth = window.innerWidth; const threshold = viewportWidth * 0.3; if (rect.left > viewportWidth - threshold) { setButtonPosition(viewportWidth - rect.width, rect.top); button.classList.add('docked'); } else if (rect.left < threshold) { setButtonPosition(0, rect.top); button.classList.add('docked'); } } function drag(e) { if (!isDragging) return; e.preventDefault(); if (dragThrottle) return; dragThrottle = setTimeout(() => { dragThrottle = null; }, 16); const { x: currentX, y: currentY } = getEventPosition(e); const deltaX = currentX - startX; const deltaY = currentY - startY; requestAnimationFrame(() => { setButtonPosition(initialX + deltaX, initialY + deltaY); lastValidX = initialX + deltaX; lastValidY = initialY + deltaY; }); } button.addEventListener('touchstart', dragStart, { passive: false }); button.addEventListener('touchend', dragEnd, { passive: false }); button.addEventListener('touchmove', drag, { passive: false }); button.addEventListener('mousedown', dragStart); document.addEventListener('mousemove', drag); document.addEventListener('mouseup', dragEnd); window.addEventListener('resize', () => { if (lastValidX !== undefined && lastValidY !== undefined) { setButtonPosition(lastValidX, lastValidY); } }); const savedPosition = GM_getValue('buttonPosition'); if (savedPosition) { setButtonPosition(savedPosition.x, savedPosition.y); } else { setButtonPosition( window.innerWidth - button.offsetWidth - 20, window.innerHeight / 2 - button.offsetHeight / 2 ); } } function createModalContainer(title, contentElement, footerElement) { const container = createElement('div'); const header = createElement('div', { className: 'modal-header', textContent: title }); container.appendChild(header); container.appendChild(contentElement); container.appendChild(footerElement); return container; } function createFormGroup(labelText, inputOptions) { const group = createElement('div', { className: 'form-group' }); const label = createElement('label', { className: 'form-label', textContent: labelText }); let input; if (inputOptions.type === 'textarea') { input = createElement('textarea', inputOptions); } else { input = createElement('input', inputOptions); } group.appendChild(label); group.appendChild(input); return { group, input }; } function createPromptModal({ title, promptId, promptContent, isEditable, onSave, modal, overlay }) { const content = createElement('div', { className: 'modal-content' }); const { group: idGroup, input: idInput } = createFormGroup('Prompt ID', { className: 'form-input', type: 'text', value: promptId || '', placeholder: '为提示词命名' }); if (promptId && !isEditable) { idInput.disabled = true; } content.appendChild(idGroup); const { group: contentGroup, input: contentInput } = createFormGroup('Prompt Content', { className: 'form-textarea', type: 'textarea', value: promptContent || '', placeholder: '输入提示词的内容' }); contentInput.value = promptContent || ''; content.appendChild(contentGroup); const footerButtons = [ { text: 'Cancel', onClick: (modal, overlay) => hideModal(modal, overlay) }, { text: promptId ? 'Update' : 'Save', onClick: () => { const idVal = idInput.value.trim(); const contentVal = contentInput.value.trim(); onSave(idVal, contentVal); } } ]; const footer = createModalFooter(footerButtons, modal, overlay); const container = createModalContainer(title, content, footer); openModal(modal, overlay, container); setTimeout(() => contentInput.focus(), 100); } function showNewPromptModal(modal, overlay) { createPromptModal({ title: 'New Prompt', promptId: '', promptContent: '', isEditable: true, onSave: (id, content) => { if (id && content) { try { saveNewPrompt(id, content); hideModal(modal, overlay); } catch (err) { debug.error('提示词保存失败:', err); alert('保存失败,请重试'); } } else { alert('请填写所有必填字段'); } }, modal, overlay }); } function showEditPromptModal(modal, overlay, id, content) { createPromptModal({ title: 'Prompt Editing', promptId: id, promptContent: content, isEditable: false, onSave: (id, newContent) => { if (newContent) { savedPrompts[id] = newContent; GM_setValue('prompts', savedPrompts); hideModal(modal, overlay); showManagePromptsModal(modal, overlay); updateDatalist(); } else { alert('内容不能为空'); } }, modal, overlay }); } function showManagePromptsModal(modal, overlay) { const container = createElement('div'); const header = createElement('div', { className: 'modal-header', textContent: 'Manage Prompts' }); container.appendChild(header); const content = createElement('div', { className: 'modal-content' }); Object.entries(savedPrompts).forEach(([id, promptContent]) => { const promptGroup = createElement('div', { className: 'form-group', style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '8px', borderBottom: '1px solid #eee' } }); const promptId = createElement('div', { className: 'form-label', style: { margin: '0', flex: '1' }, textContent: id }); const buttonGroup = createElement('div', { className: 'button-group', style: { marginLeft: '16px' } }); const editButton = createElement('button', { className: 'panel-button', textContent: 'Edit', onclick: () => { showEditPromptModal(modal, overlay, id, promptContent); } }); const deleteButton = createElement('button', { className: 'panel-button', textContent: 'Delete', onclick: () => { if (confirm(`确定要删除 "${id}" 吗?`)) { delete savedPrompts[id]; GM_setValue('prompts', savedPrompts); promptGroup.remove(); updateDatalist(); } } }); buttonGroup.appendChild(editButton); buttonGroup.appendChild(deleteButton); promptGroup.appendChild(promptId); promptGroup.appendChild(buttonGroup); content.appendChild(promptGroup); }); container.appendChild(content); const footer = createElement('div', { className: 'modal-footer' }); const closeButton = createElement('button', { className: 'panel-button', textContent: 'Close', onclick: () => { hideModal(modal, overlay); } }); footer.appendChild(closeButton); container.appendChild(footer); openModal(modal, overlay, container); } function updateDatalist() { const datalist = document.getElementById('prompt-options'); if (!datalist) return; debug.log('最新的提示词列表:', savedPrompts); datalist.textContent = ''; Object.keys(savedPrompts).forEach(id => { const option = createElement('option', { value: id }); datalist.appendChild(option); }); } function setupEventListeners(button, panel, modal, overlay) { panel.addEventListener('click', (e) => { e.stopPropagation(); }); button.addEventListener('click', (e) => { if(!e.target.classList.contains('dragging')) { e.stopPropagation(); panel.classList.toggle('visible'); } }); document.addEventListener('click', () => { if (panel.classList.contains('visible')) { panel.classList.remove('visible'); } }); panel.addEventListener('click', (e) => { const btn = e.target.closest('button[data-action]'); if (!btn) return; const action = btn.dataset.action; try { if (action === 'new') { showNewPromptModal(modalRef, overlayRef); } else if (action === 'manage') { showManagePromptsModal(modalRef, overlayRef); } } catch (error) { debug.error('无效的点击操作或按钮绑定的操作异常', error); } }); const promptInput = panel.querySelector('.prompt-input'); promptInput.addEventListener('change', async (e) => { const selectedValue = e.target.value.trim(); const promptContent = savedPrompts[selectedValue]; if (promptContent) { debug.log('已选择提示词:', selectedValue); try { const site = getCurrentSite(); const success = await setPromptWithRetry(site, promptContent); if (!success) { alert('提示词输入失败,请重试'); } } catch(err) { alert('提示词输入过程中出现如下错误:' + err.message); } } e.target.value = ''; }); overlay.addEventListener('click', () => { hideModal(modal, overlay); }); document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && modal.classList.contains('visible')) { hideModal(modal, overlay); } }); } async function initialize() { debug.log('正在初始化...'); try { const savedSyncConfig = GM_getValue('syncConfig'); if (savedSyncConfig) { CONFIG.sync = { ...CONFIG.sync, ...savedSyncConfig }; } const currentSite = getCurrentSite(); savedPrompts = loadPrompts(); const siteConfig = Object.values(CONFIG.sites).find(s => s.id === currentSite); if (!siteConfig) { debug.error('缺少当前站点的相关配置'); return; } const editorSelector = siteConfig.selector; try { await waitForElement(editorSelector); } catch(err) { debug.error('找不到与配置匹配的文本编辑器元素:', err); return; } initializeUI(); } catch (error) { debug.error('初始化过程中发生错误:', error); } } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initialize); } else { initialize(); } })();