幻觉(Illusion)

幻觉(Illusion)是一个精简的跨平台 Prompts 管理工具,支持在以下 AI 平台使用:Google AI Studio, OpenAI ChatGPT, Anthropic Claude 和 DeepSeek Chat。

目前為 2025-02-19 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         幻觉(Illusion)
// @icon         https://raw.githubusercontent.com/cattail-mutt/Illusion/refs/heads/main/resources/icons/illusion.png
// @namespace    LINUX_DO
// @version      1.2
// @description  幻觉(Illusion)是一个精简的跨平台 Prompts 管理工具,支持在以下 AI 平台使用:Google AI Studio, OpenAI ChatGPT, Anthropic Claude 和 DeepSeek Chat。
// @author       Mukai
// @license      MIT
// @match        https://aistudio.google.com/*
// @match        https://chatgpt.com/*
// @match        https://claude.ai/*
// @match        https://chat.deepseek.com/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_getResourceText
// @resource     PROMPTS https://raw.githubusercontent.com/cattail-mutt/Illusion/refs/heads/main/resources/config/prompts.yaml
// @resource     THEMES https://raw.githubusercontent.com/cattail-mutt/Illusion/refs/heads/main/resources/config/themes.json
// @resource     CSS https://raw.githubusercontent.com/cattail-mutt/Illusion/refs/heads/main/resources/styles/illusion.css
// @require      https://cdnjs.cloudflare.com/ajax/libs/js-yaml/4.1.0/js-yaml.min.js
// @homepage     https://greasyfork.org/zh-CN/scripts/527451-%E5%B9%BB%E8%A7%89-illusion
// ==/UserScript==

(function() {
    'use strict';

    let modalRef = null;
    let overlayRef = null;
    let savedPrompts = {};
    
    window.onerror = function(msg, url, line, col, error) {
        console.error('[Illusion]错误:', {msg, url, line, col, error});
        return false;
    };
    
    const debug = {
        enabled: true,
        log: (...args) => debug.enabled && console.log('[Illusion]日志:', ...args),
        error: (...args) => console.error('[Illusion]错误:', ...args),
        warn: (...args) => console.warn('[Illusion]警告:', ...args),
        trace: (...args) => debug.enabled && console.trace('[Illusion]追踪:', ...args)
    };

    const initialPrompts = jsyaml.load(GM_getResourceText('PROMPTS'));
    const THEMECONFIG = JSON.parse(GM_getResourceText('THEMES'));
    debug.log('[Illusion]日志: PROMPTS解析结果:', initialPrompts);
    debug.log('[Illusion]日志: 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);
        });
    }

    // ChatGPT 和 Claude 均使用了 ProseMirror 库构建富文本编辑器
    function createParagraph(line) {
        const p = document.createElement('p');
        if (line.trim()) {
            p.textContent = line;
        } else {
            p.innerHTML = '<br>';
        }
        return p;
    }

    const updateProseMirror = (editor, prompt) => {
        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']);
    };

    // Gemini 和 DeepSeek 使用的均是纯文本输入框 <textarea>
    // DeepSeek 不支持对 textarea.value 直接更新
    const updateTextArea = async (textarea, prompt) => {
        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 = {
        debugEnabled: true,
        maxRetries: 3,
        retryDelay: 1000,
        initTimeout: 10000,
        eventDelay: 50,
        eventTimeout: 1000,
        sites: {
            CHATGPT: {
                id: 'chatgpt',
                icon: 'https://raw.githubusercontent.com/cattail-mutt/Illusion/refs/heads/main/resources/icons/chatgpt.svg',
                buttonSize: '48px',
                selector: 'div.ProseMirror[contenteditable=true]',
                setPrompt: updateProseMirror
            },
            CLAUDE: {
                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
            },
            GEMINI: {
                id: 'gemini',
                icon: 'https://www.gstatic.com/lamda/images/gemini_sparkle_v002_d4735304ff6292a690345.svg',
                buttonSize: '48px',
                selector: 'textarea[aria-label="Type something"]',
                setPrompt: updateTextArea
            },
            DEEPSEEK: {
                id: 'deepseek',
                icon: 'https://raw.githubusercontent.com/cattail-mutt/Illusion/refs/heads/main/resources/icons/deepseek.svg',
                buttonSize: '48px',
                selector: 'textarea[id="chat-input"]',
                setPrompt: updateTextArea
            }
        }
    };

    function loadExternalCSS() {
        const style = document.createElement('style');
        style.textContent = GM_getResourceText('CSS');
        document.head.appendChild(style);
    }

    function applyThemeVariables() {
        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('--button-size', config.buttonSize);
        root.style.setProperty('--button-bg', theme.button.bg);
        root.style.setProperty('--border-color', theme.border);
        root.style.setProperty('--button-hover', theme.button.hover);
        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);
        root.style.setProperty('--text-color', theme.text);
        root.style.setProperty('--secondary-bg', theme.secondary);
    }

    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(`Timeout waiting for ${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;
    }

    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 loadPrompts() {
        const storedPrompts = GM_getValue('prompts');
        if (!storedPrompts) {
            savedPrompts = initialPrompts;
            GM_setValue('prompts', savedPrompts);
        } else {
            savedPrompts = storedPrompts;
        }
        return savedPrompts;
    }

    function saveNewPrompt(id, content) {
        savedPrompts[id] = content;
        GM_setValue('prompts', savedPrompts);
        updateDatalist();
        debug.log('New prompt saved:', 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(`No prompt setter configured for site: ${site}`));
                    }
                    const editor = document.querySelector(config.selector);
                    if(!editor) {
                        throw new Error('Editor element not found');
                    }
                    await config.setPrompt(editor, prompt);
                    resolve(true);
                } catch (err) {
                    if (attempts < maxRetries) {
                        attempts++;
                        setTimeout(trySetPrompt, CONFIG.retryDelay);
                    } else {
                        reject(err);
                    }
                }
            };
            trySetPrompt();
        });
    }

    function initializeUI() {
        loadExternalCSS();
        applyThemeVariables();
        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);
        debug.log('Button created');
        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 Prompt',
            'data-action': 'new',
            onclick: () => {
                debug.log('New prompt button clicked');
                const modal = document.querySelector('.modal');
                const overlay = document.querySelector('.modal-overlay');
                if (modal && overlay) {
                    showNewPromptModal(modal, overlay);
                } else {
                    debug.error('Modal or overlay elements not found');
                }
            }
        });
        buttonGroup.appendChild(newButton);
        const manageButton = createElement('button', {
            className: 'panel-button',
            textContent: 'Manage',
            'data-action': 'manage',
            onclick: () => {
                debug.log('Manage button clicked');
                const modal = document.querySelector('.modal');
                const overlay = document.querySelector('.modal-overlay');
                if (modal && overlay) {
                    showManagePromptsModal(modal, overlay);
                } else {
                    debug.error('Modal or overlay elements not found');
                }
            }
        });
        buttonGroup.appendChild(manageButton);
        panel.appendChild(buttonGroup);
        document.body.appendChild(panel);
        debug.log('Panel created');
        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: '输入Prompt的标识名称'
        });
        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: '输入Prompt内容'
        });
        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('Error saving new prompt:', 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;
        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();
                debug.log('Button clicked');
                panel.classList.toggle('visible');
            }
        });

        document.addEventListener('click', () => {
            if (panel.classList.contains('visible')) {
                debug.log('Clicking outside panel, hiding panel');
                panel.classList.remove('visible');
            }
        });

        panel.addEventListener('click', (e) => {
            const btn = e.target.closest('button[data-action]');
            if (!btn) return;
            
            const action = btn.dataset.action;
            debug.log('Panel button clicked:', action);
            
            try {
                if (action === 'new') {
                    showNewPromptModal(modalRef, overlayRef);
                } else if (action === 'manage') {
                    showManagePromptsModal(modalRef, overlayRef);
                }
            } catch (error) {
                debug.error('Error handling button click:', 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('Setting prompt:', selectedValue);
                try {
                    const site = getCurrentSite();
                    const success = await setPromptWithRetry(site, promptContent);
                    if (success) {
                        debug.log('Set prompt: Done');
                    } else {
                        debug.error('Set prompt: Failed');
                        alert('Failed to set prompt. Please try again.');
                    }
                } catch(err) {
                    debug.error('Error setting prompt:', err);
                    alert('Error setting prompt: ' + 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('Initializing...');
        try {
            const currentSite = getCurrentSite();
            if(!currentSite) {
                debug.error('Unsupported site');
                return;
            }
            savedPrompts = loadPrompts();
            const siteConfig = Object.values(CONFIG.sites).find(s => s.id === currentSite);
            if (!siteConfig) {
                debug.error('Site configuration not found for current site');
                return;
            }
            const editorSelector = siteConfig.selector;
            try {
                await waitForElement(editorSelector);
                debug.log('Editor element found');
            } catch(err) {
                debug.error('Editor element not found:', err);
                return;
            }
            initializeUI();
            debug.log('Initialization complete');
        } catch (error) {
            debug.error('Initialization failed:', error);
        }
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', initialize);
    } else {
        initialize();
    }
})();