// ==UserScript==
// @name 幻觉(Illusion)
// @icon https://raw.githubusercontent.com/cattail-mutt/Illusion/refs/heads/main/resources/icons/illusion.png
// @namespace https://github.com/cattail-mutt
// @version 1.3
// @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://gf.qytechs.cn/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('PROMPTS解析结果:', initialPrompts);
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);
});
}
// 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,
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/resources/icons/chatgpt.svg',
buttonSize: '48px',
selector: 'div.ProseMirror[contenteditable=true]',
setPrompt: updateProseMirror
},
CLAUDE: {
id: 'claude',
// 由于 claude.ai 的 CSP 限制,用 svg 塞入图标
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(`超时:使用选择器 ${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 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, initialPrompts) {
if (!CONFIG.sync.enabled) {
debug.log('提示词同步功能已禁用');
return storedPrompts;
}
debug.log('提示词库同步中...');
const filteredInitialPrompts = filterPromptsByBlacklist(initialPrompts);
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(initialPrompts);
savedPrompts = filteredPrompts;
debug.log('未发现 GM 存储中的提示词,将使用初始化时加载的默认提示词:', savedPrompts);
GM_setValue('prompts', savedPrompts);
} else {
debug.log('发现 GM 存储中已有提示词如下', storedPrompts);
savedPrompts = syncPrompts(storedPrompts, initialPrompts);
}
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() {
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);
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: () => {
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 => {
debug.log('可用提示词:', 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();
}
})();