Greasy Fork

Greasy Fork is available in English.

AI 增强助手

Gemini:多轮对话跳转目录 + 去水印图片保存 + 提示词库;GPT:多轮对话跳转目录 + 提示词库;Qwen/DeepSeek/豆包/元宝:提示词库。提示词库多模型可共享。

当前为 2026-01-17 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         AI 增强助手
// @namespace    http://tampermonkey.net/
// @version      1.0.0
// @description  Gemini:多轮对话跳转目录 + 去水印图片保存 + 提示词库;GPT:多轮对话跳转目录 + 提示词库;Qwen/DeepSeek/豆包/元宝:提示词库。提示词库多模型可共享。
// @author       Mrchen
// @match        https://gemini.google.com/*
// @match        https://chatgpt.com/*
// @match        https://chat.deepseek.com/*
// @match        https://www.doubao.com/*
// @match        https://chat.qwen.ai/*
// @match        https://www.qianwen.com/*
// @match        https://yuanbao.tencent.com/*
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @grant        GM_xmlhttpRequest
// @homepageURL  https://github.com/Mrchen-1600/AI-Enhancer
// @license      Apache-2.0
// ==/UserScript==

(function() {
    'use strict';

    const HOST = location.hostname;
    console.log(`AI 增强助手 v1.0.0: 初始化中... 当前站点: ${HOST}`);

    // ================= 0 站点识别与差异化配置 =================

    // 定义不同站点的功能开关和选择器
    const SITES = {
        gemini: {
            domain: 'gemini.google.com',
            hasTOC: true,
            hasWatermark: true,
            tocSelector: '.user-query-container, [data-test-id="user-query"]',
            inputSelector: 'rich-textarea > div, div[role="textbox"], div[contenteditable], textarea',
            tocIgnoreSelectors: 'img, button, [role="button"], svg, a[target="_blank"], .attachment-preview, mat-chip'
        },
        chatgpt: {
            domain: 'chatgpt.com',
            hasTOC: true,
            hasWatermark: false,
            tocSelector: 'div[data-message-author-role="user"]',
            inputSelector: '#prompt-textarea',
            tocIgnoreSelectors: 'button, img, svg, .text-xs, [aria-label*="Attachment"]'
        },
        // --- 仅开启提示词功能 ---
        deepseek: {
            domain: 'chat.deepseek.com',
            hasTOC: false, hasWatermark: false,
            inputSelector: 'textarea, #chat-input'
        },
        doubao: {
            domain: 'www.doubao.com',
            hasTOC: false, hasWatermark: false,
            inputSelector: 'textarea, [contenteditable="true"]'
        },
        tongyi: {
            domain: 'qianwen.com',
            extraDomain: 'https://chat.qwen.ai',
            hasTOC: false, hasWatermark: false,
            inputSelector: 'textarea, .ant-input, #chat-input, [contenteditable="true"]'
        },
        yuanbao: {
            domain: 'yuanbao.tencent.com',
            hasTOC: false, hasWatermark: false,
            inputSelector: 'div[contenteditable="true"], textarea'
        }
    };

    let CURRENT_SITE = SITES.gemini;
    let isMatchFound = false;

    for (const key in SITES) {
        const site = SITES[key];
        if (HOST.includes(site.domain) || (site.extraDomain && HOST.includes(site.extraDomain))) {
            CURRENT_SITE = site;
            isMatchFound = true;
            break;
        }
    }

    const CONFIG = {
        watermarkRegion: { width: 160, height: 65 },
        pollInterval: 1000,
        btnOpacityIdle: '0.5',
        btnOpacityHover: '1.0'
    };

    // ================= 1 样式定义 =================
    const STYLES = `
        /* 悬浮按钮 */
        .gemini-float-btn {
            position: fixed; z-index: 2147483647; cursor: grab; /* Max Z-Index */
            transition: transform 0.2s, opacity 0.3s; opacity: ${CONFIG.btnOpacityIdle};
            box-shadow: 0 4px 12px rgba(0,0,0,0.5); user-select: none;
            display: flex; align-items: center; justify-content: center;
            font-size: 20px; color: #fff; background: #2d2e30;
            border-radius: 50%; border: 1px solid #555;
            width: 45px; height: 45px;
        }
        .gemini-float-btn:hover { opacity: ${CONFIG.btnOpacityHover}; transform: scale(1.1); }
        .gemini-float-btn:active { cursor: grabbing; }
        .gemini-float-btn::before { content: ''; position: absolute; top: -20px; bottom: -20px; left: -20px; right: -20px; z-index: -1; }
        .docked-right { right: -20px !important; } .docked-right:hover { right: 10px !important; }
        .docked-left { left: -20px !important; } .docked-left:hover { left: 10px !important; }

        /* 窗口样式 */
        .gemini-window {
            position: fixed; background: #1e1f20; border: 1px solid #444; border-radius: 12px;
            z-index: 2147483647; color: #e3e3e3; box-shadow: 0 10px 40px rgba(0,0,0,0.8);
            display: none; flex-direction: column; overflow: hidden; resize: both; min-width: 250px; min-height: 200px;
            font-family: sans-serif;
        }
        .window-header {
            padding: 10px 15px; background: #2d2e30; border-bottom: 1px solid #444;
            cursor: grab; display: flex; justify-content: space-between; align-items: center; user-select: none; flex-shrink: 0;
        }
        .window-header:active { cursor: grabbing; background: #3c4043; }
        .window-title { font-weight: bold; font-size: 14px; pointer-events: none; }
        .window-close { background: none; border: none; color: #999; cursor: pointer; font-size: 16px; padding: 0 5px; }
        .window-close:hover { color: #fff; }
        .window-content { flex: 1; overflow-y: auto; padding: 10px; position: relative; }

        /* 目录样式 */
        .toc-item { padding: 6px 10px; margin-bottom: 2px; border-radius: 4px; font-size: 13px; color: #c4c7c5; cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; border-left: 2px solid transparent; }
        .toc-item:hover { background: #3c4043; border-left-color: #8ab4f8; color: #fff; }

        /* 提示词样式 */
        .pm-layout { display: flex; height: 100%; flex-direction: column; }
        .pm-body { flex: 1; display: flex; overflow: hidden; min-height: 0; }
        .pm-sidebar { width: 150px; background: #252628; border-right: 1px solid #333; display: flex; flex-direction: column; padding: 10px; gap: 5px; overflow-y: auto; }
        .pm-main { flex: 1; padding: 10px; overflow-y: auto; background: #1e1f20; display: flex; flex-direction: column; gap: 8px; }
        .pm-footer { padding: 10px; background: #252628; border-top: 1px solid #333; display: flex; gap: 8px; flex-shrink: 0; align-items: center; }
        .pm-input { background: #1e1f20; border: 1px solid #444; color: #fff; padding: 6px; border-radius: 4px; outline: none; font-size: 13px; }

        .tag-item {
            padding: 5px 8px; cursor: pointer; border-radius: 4px; font-size: 12px; color: #aaa;
            display: flex; justify-content: space-between; align-items: center; user-select: none;
        }
        .tag-item:hover { background: #333; }
        .tag-item.active { background: #3c4043; color: #fff; font-weight: bold; }
        .tag-item.dragging { opacity: 0.5; background: #444; border: 1px dashed #666; }
        .tag-item.drag-over { border-top: 2px solid #8ab4f8; }

        .prompt-card {
            background: #2a2b2d; padding: 10px; border-radius: 6px; border: 1px solid #3c4043;
            display: flex; flex-direction: column; gap: 5px; user-select: none;
            transition: transform 0.2s, box-shadow 0.2s;
        }
        .prompt-card:hover { border-color: #555; }
        .prompt-card.pinned { border-color: #fbbc04; background: #2d2e25; }
        .prompt-card.dragging { opacity: 0.4; border: 2px dashed #8ab4f8; transform: scale(0.98); }
        .prompt-card.drag-over-top { border-top: 2px solid #8ab4f8; }
        .prompt-card.drag-over-bottom { border-bottom: 2px solid #8ab4f8; }

        .card-tag { font-size: 11px; background: #333; padding: 2px 5px; border-radius: 3px; width: fit-content; }
        .card-text { font-size: 12px; color: #ddd; display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; cursor: pointer; white-space: pre-wrap; }
        .card-text:hover { color: #fff; }
        .card-actions { display: flex; justify-content: space-between; align-items: center; gap: 5px; margin-top: 5px; }
        .card-actions-right { display: flex; gap: 5px; }

        .pm-btn { padding: 3px 8px; border-radius: 3px; border: none; cursor: pointer; font-size: 11px; }
        .btn-green { background: #264c2d; color: #a8dab5; }
        .btn-blue { background: #1a73e8; color: #fff; }
        .btn-red { background: transparent; color: #ffadad; }
        .btn-pin { background: transparent; border: 1px solid #555; color: #888; }
        .btn-pin.active { color: #fbbc04; border-color: #fbbc04; }
        .btn-icon { background: transparent; border: none; color: #888; cursor: pointer; font-size: 12px; padding: 2px; }
        .btn-icon:hover { color: #fff; }

        .edit-overlay { position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.8); z-index: 10; display: flex; flex-direction: column; padding: 10px; gap: 10px; }
        .edit-overlay textarea { flex: 1; background: #111; color: #fff; border: 1px solid #555; resize: none; padding: 5px; }

        .dl-btn {
            position: absolute; top: 8px; left: 8px;
            background: rgba(0,0,0,0.85); color: #fff;
            padding: 5px 10px; border-radius: 4px; border: 1px solid rgba(255,255,255,0.2);
            cursor: pointer; font-size: 11px; z-index: 50;
            backdrop-filter: blur(4px); transition: all 0.2s;
        }
        .dl-btn:hover { background: #1a73e8; border-color: #1a73e8; }

        .about-overlay { position: fixed; top:0; left:0; width:100vw; height:100vh; background: rgba(0,0,0,0.7); z-index: 2147483647; display:flex; justify-content:center; align-items:center; }
        .about-modal { background: #1e1f20; padding: 25px; border-radius: 12px; border: 1px solid #555; width: 380px; color: #e3e3e3; display: flex; flex-direction: column; gap: 15px; box-shadow: 0 10px 50px rgba(0,0,0,0.8); text-align: center; }
        .about-title { font-size: 18px; font-weight: bold; color: #fff; margin-bottom: 5px; }
        .about-desc { font-size: 13px; color: #aaa; line-height: 1.5; }
        .star-btn { background: #2ea043; color: white; border: none; padding: 10px 20px; border-radius: 6px; font-size: 14px; font-weight: bold; cursor: pointer; text-decoration: none; display: inline-flex; align-items: center; justify-content: center; gap: 8px; transition: background 0.2s; }
        .star-btn:hover { background: #2c974b; }
        .close-btn { background: transparent; border: 1px solid #555; color: #888; padding: 6px 12px; border-radius: 6px; cursor: pointer; font-size: 12px; align-self: center; margin-top: 10px; }
        .close-btn:hover { color: #fff; border-color: #777; }
    `;

    GM_addStyle(STYLES);

    // ================= 2 状态与存储 =================
    const DEFAULT_LAYOUT = {
        toc: { x: window.innerWidth - 260, y: 150, w: 220, h: 400 },
        prompt: { x: window.innerWidth / 2 - 350, y: window.innerHeight / 2 - 250, w: 700, h: 500 }
    };

    function saveState(key, element) {
        const rect = element.getBoundingClientRect();
        GM_setValue(key, JSON.stringify({ x: rect.left, y: rect.top, w: rect.width, h: rect.height }));
    }

    function loadState(key, element, defaultVal) {
        try {
            const val = JSON.parse(GM_getValue(key, 'null')) || defaultVal;
            const x = Math.min(Math.max(0, val.x), window.innerWidth - 50);
            const y = Math.min(Math.max(0, val.y), window.innerHeight - 50);
            element.style.left = `${x}px`; element.style.top = `${y}px`;
            element.style.width = `${val.w}px`; element.style.height = `${val.h}px`;
        } catch(e) {}
    }

    function getPrompts() { try { return JSON.parse(GM_getValue('prompts_v3', '[]')); } catch{ return []; } }
    function setPrompts(list) { GM_setValue('prompts_v3', JSON.stringify(list)); }
    function getTagOrder() { try { return JSON.parse(GM_getValue('tag_sort_order', '[]')); } catch { return []; } }
    function setTagOrder(list) { GM_setValue('tag_sort_order', JSON.stringify(list)); }

    // ================= 3 DOM 工具 =================
    function el(tag, className, children, clickHandler) {
        const elem = document.createElement(tag);
        if (className) elem.className = className;
        if (typeof children === 'string') elem.textContent = children;
        else if (Array.isArray(children)) children.forEach(c => c && elem.appendChild(c));
        else if (children) elem.appendChild(children);
        if (clickHandler) elem.addEventListener('click', e => { e.stopPropagation(); clickHandler(e); });
        return elem;
    }

    function makeWindowDraggable(headerEl, windowEl, storageKey) {
        headerEl.addEventListener('mousedown', (e) => {
            const startX = e.clientX, startY = e.clientY;
            const rect = windowEl.getBoundingClientRect();
            const startLeft = rect.left, startTop = rect.top;
            const onMove = (me) => {
                windowEl.style.left = `${startLeft + (me.clientX - startX)}px`;
                windowEl.style.top = `${startTop + (me.clientY - startY)}px`;
                windowEl.style.right = 'auto'; windowEl.style.bottom = 'auto';
            };
            const onUp = () => {
                document.removeEventListener('mousemove', onMove);
                document.removeEventListener('mouseup', onUp);
                saveState(storageKey, windowEl);
            };
            document.addEventListener('mousemove', onMove);
            document.addEventListener('mouseup', onUp);
        });
        windowEl.addEventListener('mouseup', () => saveState(storageKey, windowEl));
    }
    function makeButtonDraggable(btn, onDragEnd) {
        let isDragging = false, startX, startY, initLeft, initTop;
        btn.addEventListener('mousedown', e => {
            isDragging = false; startX = e.clientX; startY = e.clientY;
            const rect = btn.getBoundingClientRect(); initLeft = rect.left; initTop = rect.top;
            btn.classList.remove('docked-right', 'docked-left');
            btn.style.right = 'auto'; btn.style.left = `${initLeft}px`; btn.style.top = `${initTop}px`;
            const onMove = me => {
                if (Math.abs(me.clientX - startX) > 3) isDragging = true;
                btn.style.left = `${initLeft + (me.clientX - startX)}px`;
                btn.style.top = `${initTop + (me.clientY - startY)}px`;
            };
            const onUp = () => {
                document.removeEventListener('mousemove', onMove);
                document.removeEventListener('mouseup', onUp);
                if (isDragging) {
                    const r = btn.getBoundingClientRect();
                    if (r.left + r.width/2 > window.innerWidth/2) {
                        btn.style.left = 'auto'; btn.style.right = '10px';
                        setTimeout(() => btn.classList.add('docked-right'), 500);
                    } else {
                        btn.style.left = '10px';
                        setTimeout(() => btn.classList.add('docked-left'), 500);
                    }
                } else if (onDragEnd) onDragEnd();
            };
            document.addEventListener('mousemove', onMove);
            document.addEventListener('mouseup', onUp);
        });
    }

    // ================= 4 Star 面板 =================
    function initAboutPanel() {
        GM_registerMenuCommand("⭐ 支持作者 / Star", showAboutModal);
    }

    function showAboutModal() {
        if (document.getElementById('enhancer-about-modal')) return;
        const starLink = el('a', 'star-btn', '⭐ Star on GitHub');
        starLink.href = 'https://github.com/Mrchen-1600/AI-Enhancer';
        starLink.target = '_blank';
        const overlay = el('div', 'about-overlay', [
            el('div', 'about-modal', [
                el('div', 'about-title', `🚀 AI 增强助手 (${CURRENT_SITE.domain})`),
                el('div', 'about-desc', '如果觉得好用,请到 GitHub 仓库点亮一颗 Star 支持作者吧!'),
                starLink,
                el('button', 'close-btn', '关闭窗口', () => overlay.remove())
            ])
        ]);
        overlay.id = 'enhancer-about-modal';
        document.body.appendChild(overlay);
    }

    // ================= 5 目录模块 =================
    let tocPanel, tocList, lastTOCData = "";

    function getUniqueQueries() {
        const containers = document.querySelectorAll(CURRENT_SITE.tocSelector);
        const unique = [];
        const seen = new Set();

        containers.forEach(c => {
            const clone = c.cloneNode(true);
            if (CURRENT_SITE.tocIgnoreSelectors) {
                try {
                    const garbage = clone.querySelectorAll(CURRENT_SITE.tocIgnoreSelectors);
                    garbage.forEach(g => g.remove());
                } catch(e) {}
            }
            const rawText = clone.innerText || "";
            const t = rawText.replace(/\s+/g, ' ').trim();

            if (t.length > 1 && !seen.has(t)) {
                seen.add(t);
                unique.push({el: c, text: t});
            }
        });
        return unique;
    }

    function initTOC() {
        if (!CURRENT_SITE.hasTOC) return;

        if (document.getElementById('gemini-toc-btn')) return;
        const btn = el('div', 'gemini-float-btn docked-right', '📂');
        btn.id = 'gemini-toc-btn'; btn.style.top = '150px'; btn.style.right = '-20px';
        document.body.appendChild(btn);

        tocPanel = el('div', 'gemini-window', [
            el('div', 'window-header', [el('span', 'window-title', '对话目录'), el('button', 'window-close', '✕', () => tocPanel.style.display = 'none')]),
            tocList = el('div', 'window-content', '')
        ]);
        tocPanel.id = 'gemini-toc-panel'; document.body.appendChild(tocPanel);
        loadState('toc_state', tocPanel, DEFAULT_LAYOUT.toc);
        makeWindowDraggable(tocPanel.querySelector('.window-header'), tocPanel, 'toc_state');

        makeButtonDraggable(btn, () => {
            if (tocPanel.style.display === 'flex') tocPanel.style.display = 'none';
            else { updateTOCList(true); tocPanel.style.display = 'flex'; saveState('toc_state', tocPanel); }
        });
    }

    function updateTOCList(force = false) {
        if (!tocPanel || tocPanel.style.display === 'none') return;

        const unique = getUniqueQueries();
        const currentData = unique.map(u => u.text).join('|');
        if (!force && currentData === lastTOCData) return;
        lastTOCData = currentData;

        while(tocList.firstChild) tocList.removeChild(tocList.firstChild);
        if (unique.length === 0) { tocList.appendChild(el('div', '', '暂无对话 (尝试刷新或提问)')); return; }

        unique.forEach((item, idx) => {
            tocList.appendChild(el('div', 'toc-item', `${idx+1}. ${item.text}`, () => {
                // Live lookup for stale elements
                const liveUnique = getUniqueQueries();
                const target = liveUnique[idx];

                if (target && target.el && target.el.isConnected) {
                    target.el.scrollIntoView({behavior:'smooth', block:'center'});
                } else if (item.el) {
                    item.el.scrollIntoView({behavior:'smooth', block:'center'});
                }
            }));
        });
    }

    // ================= 6 提示词模块  =================
    let promptPanel, promptMain, sidebar, currentTag = '全部', currentSearch = '';
    let isTagEditMode = false;
    let dragSrcEl = null;

    function initPromptManager() {
        if (document.getElementById('gemini-prompt-btn')) return;
        const btn = el('div', 'gemini-float-btn docked-right', '💡');
        btn.id = 'gemini-prompt-btn'; btn.style.top = '220px'; btn.style.right = '-20px';
        document.body.appendChild(btn);

        promptPanel = el('div', 'gemini-window', [
            el('div', 'window-header', [el('span', 'window-title', '提示词仓库'), el('button', 'window-close', '✕', () => promptPanel.style.display = 'none')]),
            el('div', 'window-content pm-layout', [
                el('div', 'pm-body', [
                    el('div', 'pm-sidebar', [
                        el('div', '', [
                             (() => { const i = el('input', 'pm-input'); i.placeholder = '🔍 搜索'; i.style.width='100%'; i.style.marginBottom='5px'; i.addEventListener('input', e => { currentSearch = e.target.value.toLowerCase(); renderPromptList(); }); return i; })(),
                             el('button', 'pm-btn btn-blue', '⚙️ 管理标签', () => toggleTagEditMode())
                        ]),
                        sidebar = el('div', 'tag-list-container', '')
                    ]),
                    promptMain = el('div', 'pm-main')
                ]),
                el('div', 'pm-footer', [
                    (() => { const i = el('input', 'pm-input'); i.id='new-tag'; i.placeholder='新标签'; i.style.width='70px'; return i; })(),
                    (() => { const i = el('input', 'pm-input'); i.id='new-text'; i.placeholder='新增提示词...'; i.style.flex='1'; return i; })(),
                    el('button', 'pm-btn btn-green', '+新增', handleSave)
                ])
            ])
        ]);
        promptPanel.id = 'gemini-prompt-panel'; document.body.appendChild(promptPanel);
        loadState('prompt_state', promptPanel, DEFAULT_LAYOUT.prompt);
        makeWindowDraggable(promptPanel.querySelector('.window-header'), promptPanel, 'prompt_state');

        makeButtonDraggable(btn, () => {
            if (promptPanel.style.display === 'flex') promptPanel.style.display = 'none';
            else { refreshPromptUI(); promptPanel.style.display = 'flex'; }
        });
    }

    function handleSave() {
        const tag = document.getElementById('new-tag').value.trim() || '通用';
        const text = document.getElementById('new-text').value.trim();
        if(!text) return;
        const list = getPrompts();
        list.push({ id:Date.now(), tag, text, pinned:false });
        setPrompts(list);
        document.getElementById('new-text').value = '';
        refreshPromptUI();
    }

    function toggleTagEditMode() {
        isTagEditMode = !isTagEditMode;
        promptPanel.querySelector('.pm-sidebar button').textContent = isTagEditMode ? '✅ 完成' : '⚙️ 管理标签';
        refreshTags();
    }

    function refreshTags() {
        while(sidebar.firstChild) sidebar.removeChild(sidebar.firstChild);
        const prompts = getPrompts();
        const systemTags = ['全部', '置顶'];
        const userTags = new Set();
        prompts.forEach(p => userTags.add(p.tag));
        let savedOrder = getTagOrder();
        savedOrder = savedOrder.filter(t => userTags.has(t));
        userTags.forEach(t => { if(!savedOrder.includes(t)) savedOrder.push(t); });

        [...systemTags, ...savedOrder].forEach(t => {
            const isSystem = systemTags.includes(t);
            const row = el('div', `tag-item ${currentTag===t && !isTagEditMode ?'active':''}`, '');
            if (!isSystem) {
                row.draggable = true; row.dataset.tag = t;
                row.addEventListener('dragstart', (e) => { dragSrcEl = row; e.dataTransfer.effectAllowed = 'move'; e.dataTransfer.setData('text/plain', t); row.classList.add('dragging'); });
                row.addEventListener('dragend', () => { row.classList.remove('dragging'); document.querySelectorAll('.tag-item').forEach(el => el.classList.remove('drag-over')); });
                row.addEventListener('dragover', (e) => { e.preventDefault(); return false; });
                row.addEventListener('dragenter', (e) => { if (row !== dragSrcEl) row.classList.add('drag-over'); });
                row.addEventListener('dragleave', () => row.classList.remove('drag-over'));
                row.addEventListener('drop', (e) => {
                    e.stopPropagation();
                    if (dragSrcEl !== row) {
                        const srcTag = dragSrcEl.dataset.tag; const targetTag = t;
                        const currentOrder = [...savedOrder];
                        const srcIdx = currentOrder.indexOf(srcTag); const tgtIdx = currentOrder.indexOf(targetTag);
                        if (srcIdx > -1 && tgtIdx > -1) {
                            currentOrder.splice(srcIdx, 1); currentOrder.splice(tgtIdx, 0, srcTag);
                            setTagOrder(currentOrder); refreshTags();
                        }
                    }
                    return false;
                });
            }
            if (isTagEditMode && !isSystem) {
                const input = el('input', 'pm-input', ''); input.value = t; input.style.width='70px'; input.style.padding='2px';
                input.onchange = (e) => { const n = e.target.value.trim(); if(n && n!==t) renameTag(t, n); };
                const delBtn = el('button', 'btn-icon', '🗑️', () => deleteTagGroup(t));
                row.appendChild(input); row.appendChild(delBtn);
            } else {
                row.textContent = t;
                row.onclick = () => { if(!isTagEditMode) { currentTag=t; refreshPromptUI(); }};
            }
            sidebar.appendChild(row);
        });
    }

    function renameTag(oldName, newName) { if(!confirm(`重命名 "${oldName}" -> "${newName}"?`)) return refreshTags(); const list = getPrompts(); list.forEach(p => { if(p.tag === oldName) p.tag = newName; }); setPrompts(list); refreshPromptUI(); }
    function deleteTagGroup(tagName) { if(!confirm(`删除标签 "${tagName}" 及所有内容?`)) return; let list = getPrompts(); list = list.filter(p => p.tag !== tagName); setPrompts(list); refreshPromptUI(); }

    function renderPromptList() {
        while(promptMain.firstChild) promptMain.removeChild(promptMain.firstChild);
        const fullList = getPrompts();
        let displayList = fullList.filter(p => {
            const mTag = currentTag==='全部' || (currentTag==='置顶' && p.pinned) || p.tag===currentTag;
            const mSearch = p.text.toLowerCase().includes(currentSearch) || p.tag.includes(currentSearch);
            return mTag && mSearch;
        });

        const pinnedGroup = displayList.filter(p => p.pinned);
        const normalGroup = displayList.filter(p => !p.pinned);

        if(displayList.length===0) { promptMain.appendChild(el('div','','无内容')); return; }

        const renderCard = (item, isPinned) => {
            const card = el('div', `prompt-card ${item.pinned?'pinned':''}`, [
                el('div', 'card-text', item.text, () => fillInput(item.text)),
                el('div', 'card-actions', [
                    el('span', 'card-tag', item.tag), 
                    el('div', 'card-actions-right', [
                        el('button', `pm-btn btn-pin ${item.pinned?'active':''}`, '📌', ()=>togglePin(item.id)),
                        el('button', 'pm-btn btn-green', '填入', ()=>fillInput(item.text)),
                        el('button', 'pm-btn btn-blue', '编辑', ()=>editPrompt(item)),
                        el('button', 'pm-btn btn-red', '🗑️', ()=>deleteItem(item.id))
                    ])
                ])
            ]);
            card.draggable = true; card.dataset.id = item.id; card.dataset.group = isPinned ? 'pinned' : 'normal';
            card.addEventListener('dragstart', (e) => { dragSrcEl = card; e.dataTransfer.effectAllowed = 'move'; e.dataTransfer.setData('text/plain', item.id); card.classList.add('dragging'); });
            card.addEventListener('dragend', () => { card.classList.remove('dragging'); document.querySelectorAll('.prompt-card').forEach(c => c.classList.remove('drag-over-top', 'drag-over-bottom')); });
            card.addEventListener('dragover', (e) => { e.preventDefault(); if (dragSrcEl.dataset.group !== card.dataset.group) return; const rect = card.getBoundingClientRect(); const mid = rect.top + rect.height / 2; if (e.clientY < mid) { card.classList.add('drag-over-top'); card.classList.remove('drag-over-bottom'); } else { card.classList.add('drag-over-bottom'); card.classList.remove('drag-over-top'); } });
            card.addEventListener('dragleave', () => card.classList.remove('drag-over-top', 'drag-over-bottom'));
            card.addEventListener('drop', (e) => {
                e.stopPropagation();
                if (dragSrcEl !== card && dragSrcEl.dataset.group === card.dataset.group) {
                    const srcId = parseInt(dragSrcEl.dataset.id); const targetId = parseInt(card.dataset.id);
                    const list = getPrompts(); const srcIndex = list.findIndex(p => p.id === srcId); const targetIndex = list.findIndex(p => p.id === targetId);
                    if (srcIndex > -1 && targetIndex > -1) {
                        const [movedItem] = list.splice(srcIndex, 1);
                        let newTargetIndex = list.findIndex(p => p.id === targetId);
                        if (card.classList.contains('drag-over-bottom')) newTargetIndex++;
                        list.splice(newTargetIndex, 0, movedItem); setPrompts(list); renderPromptList();
                    }
                }
                return false;
            });
            return card;
        };
        pinnedGroup.forEach(item => promptMain.appendChild(renderCard(item, true)));
        normalGroup.forEach(item => promptMain.appendChild(renderCard(item, false)));
    }

    function refreshPromptUI() { refreshTags(); renderPromptList(); }
    function togglePin(id) { const list=getPrompts(); const i=list.find(p=>p.id===id); if(i){ i.pinned=!i.pinned; setPrompts(list); refreshPromptUI(); } }
    function deleteItem(id) { if(confirm('删除?')) { setPrompts(getPrompts().filter(p=>p.id!==id)); refreshPromptUI(); } }
    function editPrompt(item) {
        const overlay = el('div', 'edit-overlay', [
            el('div', '', '编辑提示词'),
            (() => { const t = el('input', 'pm-input'); t.value = item.tag; t.id='edit-tag'; return t; })(),
            (() => { const t = el('textarea', ''); t.value = item.text; t.id='edit-text'; return t; })(),
            el('div', 'card-actions', [
                el('button', 'pm-btn btn-green', '保存', () => {
                    const list = getPrompts(), target = list.find(p=>p.id===item.id);
                    if(target) { target.tag = document.getElementById('edit-tag').value.trim(); target.text = document.getElementById('edit-text').value.trim(); }
                    setPrompts(list); overlay.remove(); refreshPromptUI();
                }),
                el('button', 'pm-btn btn-red', '取消', () => overlay.remove())
            ])
        ]);
        promptPanel.querySelector('.pm-body').appendChild(overlay);
    }

    function fillInput(text) {
        const selector = CURRENT_SITE.inputSelector;
        const editor = document.querySelector(selector);

        if (!editor) return alert('未找到输入框,请先点击输入区域');

        editor.focus();

        if (editor.isContentEditable || editor.getAttribute('contenteditable') === 'true') {
            document.execCommand('selectAll', false, null);
            const success = document.execCommand('insertText', false, text);
            if (!success) {
                // Fallback direct
                editor.textContent = text;
                editor.dispatchEvent(new Event('input', { bubbles: true }));
            }
        }
        
        else if (editor.tagName === 'TEXTAREA' || editor.tagName === 'INPUT') {
            if (CURRENT_SITE.domain.includes('chatgpt')) {
                 // ChatGPT Prefer execCommand for undo history
                 document.execCommand('selectAll', false, null);
                 document.execCommand('insertText', false, text);
                 editor.dispatchEvent(new Event('input', { bubbles: true }));
            } else {
                 
                 const nativeSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, "value").set;
                 if (nativeSetter) {
                     nativeSetter.call(editor, text);
                 } else {
                     editor.value = text;
                 }
                 editor.dispatchEvent(new Event('input', { bubbles: true }));
                 editor.dispatchEvent(new Event('change', { bubbles: true }));
            }
        }
    }

    // ================= 7 图片去水印 (仅 Gemini 启用) =================
    function initImageHandler() {
        if (!CURRENT_SITE.hasWatermark) return;

        document.querySelectorAll('img').forEach(img => {
            if (img.naturalWidth > 500 && img.naturalHeight > 500 && img.width > 250 && !img.dataset.enhanced) {
                if (!img.src.includes('profile') && !img.closest('[role="button"]') && !img.closest('.mat-icon')) {
                    addDownloadBtn(img);
                }
            }
        });
    }

    function addDownloadBtn(img) {
        img.dataset.enhanced = 'true';
        const p = img.parentElement;
        if(p) {
            if(getComputedStyle(p).position==='static') p.style.position='relative';
            const btn = el('button', 'dl-btn', '✂️ 去水印保存', () => {
                btn.textContent = '处理中...'; processImageStretchBlur(img.src, btn);
            });
            p.appendChild(btn);
        }
    }

    function processImageStretchBlur(url, btn) {
        GM_xmlhttpRequest({
            method: "GET", url, responseType: "blob",
            onload: (res) => {
                if(res.status!==200) return fail(btn);
                const tempImg = new Image();
                tempImg.onload = () => {
                    const cvs = document.createElement('canvas');
                    const w = tempImg.naturalWidth, h = tempImg.naturalHeight;
                    cvs.width = w; cvs.height = h;
                    const ctx = cvs.getContext('2d');
                    ctx.drawImage(tempImg, 0, 0);
                    const wmW = CONFIG.watermarkRegion.width, wmH = CONFIG.watermarkRegion.height, wmX = w - wmW, wmY = h - wmH;
                    const sampleHeight = 20, sampleY = wmY - sampleHeight;
                    if (sampleY > 0) {
                        ctx.drawImage(cvs, wmX, sampleY, wmW, sampleHeight, wmX, wmY, wmW, wmH);
                        ctx.drawImage(cvs, wmX, wmY - 2, wmW, 2, wmX, wmY - 2, wmW, 4);
                    }
                    downloadBlob(cvs, btn);
                };
                tempImg.src = URL.createObjectURL(res.response);
            },
            onerror: () => fail(btn)
        });
    }

    function downloadBlob(blobOrCanvas, btn) {
        const save = (blob) => {
            const l = document.createElement('a'); l.href = URL.createObjectURL(blob);
            l.download = `gemini_clean_${Date.now()}.png`; document.body.appendChild(l); l.click(); document.body.removeChild(l);
            btn.textContent = '✅'; setTimeout(()=>btn.textContent='✂️ 去水印保存', 2000);
        };
        if (blobOrCanvas instanceof Blob) save(blobOrCanvas);
        else blobOrCanvas.toBlob(save, 'image/png');
    }
    function fail(btn, msg='❌') { btn.textContent = msg; setTimeout(()=>btn.textContent='✂️ 去水印保存', 2000); }

    // ================= 8 启动 =================
    function start() {
        if (CURRENT_SITE.hasTOC) initTOC();
        initPromptManager();
        initAboutPanel();

        setInterval(() => {
            if (CURRENT_SITE.hasTOC && !document.getElementById('gemini-toc-btn')) initTOC();
            if (!document.getElementById('gemini-prompt-btn')) initPromptManager();

            updateTOCList();
            initImageHandler();
        }, CONFIG.pollInterval);
    }
    window.addEventListener('load', start);
    start();

})();