Greasy Fork

Greasy Fork is available in English.

AI 提示词大师 Pro

全能整合版:在标题栏直观显示“存储模式”和“填充模式”。支持增量导入、分类折叠、Gemini 完美适配。新增面板折叠功能与UI自适应。

当前为 2025-12-23 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         AI 提示词大师 Pro
// @namespace    http://tampermonkey.net/
// @version      10.0.3
// @license      MIT
// @description  全能整合版:在标题栏直观显示“存储模式”和“填充模式”。支持增量导入、分类折叠、Gemini 完美适配。新增面板折叠功能与UI自适应。
// @author       WaterHuo
// @match        *://gemini.google.com/*
// @match        *://chatgpt.com/*
// @match        *://claude.ai/*
// @match        *://chat.deepseek.com/*
// @match        *://www.doubao.com/*
// @match        *://www.kimi.ai/*
// @match        *://www.kimi.com/*
// @match        *://kimi.moonshot.cn/*
// @match        *://grok.com/*
// @match        *://x.com/i/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_addStyle
// @grant        GM_xmlhttpRequest
// @connect      tfntmhg1.api.lncldglobal.com
// @run-at       document-start
// ==/UserScript==

(function() {
    'use strict';

    // ======= 0. 配置与工具函数 =======
    const CONFIG = {
        LC_ID: 'TFNtmHG1nSgEMcCEjz7HNGka-MdYXbMMI',
        LC_KEY: '8J1H0edw3g8BPJaTLDgS4UDm',
        API_URL: 'https://tfntmhg1.api.lncldglobal.com/1.1/classes/PromptData'
    };

    // 获取/生成用户唯一标识
    const getUserId = () => {
        let uid = GM_getValue('pm_uid');
        if (!uid) {
            uid = 'user_' + Math.random().toString(36).substr(2, 9) + Date.now().toString(36);
            GM_setValue('pm_uid', uid);
        }
        return uid;
    };
    const USER_ID = getUserId();

    // TrustedHTML 安全策略(适配Gemini)
    let ttPolicy;
    if (window.trustedTypes && window.trustedTypes.createPolicy) {
        try {
            const policyName = 'pm-policy-' + Math.random().toString(36).substring(7);
            ttPolicy = window.trustedTypes.createPolicy(policyName, {
                createHTML: (string) => string,
                createScript: (string) => string
            });
        } catch (e) {
            console.warn("PromptMaster: TrustedTypes policy already exists");
        }
    }
    const setHTML = (el, html) => {
        if (!el) return;
        el.innerHTML = ttPolicy ? ttPolicy.createHTML(html) : html;
    };

    // ======= 1. 数据管理核心 =======
    const STORAGE_KEY = 'pm_data_v16';
    const MODE_KEY = 'pm_storage_mode';
    const APPEND_KEY = 'pm_append_mode'; // 持久化追加模式
    const CLOUD_OBJ_ID_KEY = 'pm_cloud_oid';
    const FOLD_KEY = 'pm_folded_cats';
    const MINIMIZED_KEY = 'pm_is_minimized';

    let currentMode = GM_getValue(MODE_KEY, 'local'); // 默认本地存储
    let promptData = {};
    let foldedCats = GM_getValue(FOLD_KEY, []);
    let isEditMode = false;
    let appendMode = GM_getValue(APPEND_KEY, true); // 默认追加模式
    let isMinimized = GM_getValue(MINIMIZED_KEY, false);
    let isLoading = false;

    // 默认初始数据
    const defaultData = {
        "写作类": [{ name: "📝 深度润色", content: "请优化以下文本,梳理逻辑脉络,提升语言表达的简洁性和流畅性,保留核心信息。" }],
        "代码类": [{ name: "💻 逻辑审查", content: "请检查这段代码的语法错误、逻辑漏洞,给出优化建议和修复方案。" }]
    };

    // 云端API封装
    const CloudAPI = {
        headers: {
            "X-LC-Id": CONFIG.LC_ID,
            "X-LC-Key": CONFIG.LC_KEY,
            "Content-Type": "application/json"
        },
        request(method, endpoint, data = null) {
            return new Promise((resolve, reject) => {
                GM_xmlhttpRequest({
                    method: method,
                    url: endpoint.startsWith('http') ? endpoint : CONFIG.API_URL + endpoint,
                    headers: this.headers,
                    data: data ? JSON.stringify(data) : null,
                    onload: (res) => {
                        if (res.status >= 200 && res.status < 300) {
                            resolve(JSON.parse(res.responseText));
                        } else {
                            reject(JSON.parse(res.responseText));
                        }
                    },
                    onerror: reject
                });
            });
        },
        // 获取云端数据
        async fetchData() {
            const res = await this.request('GET', `?where={"uid":"${USER_ID}"}`);
            if (res.results && res.results.length > 0) {
                GM_setValue(CLOUD_OBJ_ID_KEY, res.results[0].objectId);
                return res.results[0].data;
            }
            return null;
        },
        // 保存数据到云端
        async saveData(data) {
            const oid = GM_getValue(CLOUD_OBJ_ID_KEY);
            const payload = { uid: USER_ID, data: data };
            if (oid) {
                await this.request('PUT', `/${oid}`, payload);
            } else {
                const res = await this.request('POST', '', payload);
                GM_setValue(CLOUD_OBJ_ID_KEY, res.objectId);
            }
        }
    };

    // 统一数据管理器(本地/云端切换)
    const DataManager = {
        async load() {
            isLoading = true;
            renderUI();
            try {
                if (currentMode === 'local') {
                    promptData = GM_getValue(STORAGE_KEY) || defaultData;
                } else {
                    const cloudData = await CloudAPI.fetchData();
                    promptData = cloudData || defaultData;
                }
            } catch (e) {
                toast(`加载失败: ${e.error || '网络错误'}`);
                promptData = defaultData;
            } finally {
                isLoading = false;
                renderUI();
            }
        },
        async save() {
            renderUI();
            try {
                if (currentMode === 'local') {
                    GM_setValue(STORAGE_KEY, promptData);
                    toast("✅ 本地已保存");
                } else {
                    toast("☁️ 正在同步云端...", 5000);
                    await CloudAPI.saveData(promptData);
                    toast("✅ 云端同步完成");
                }
            } catch (e) {
                toast("❌ 保存失败");
                console.error(e);
            }
        }
    };

    // ======= 2. 导入导出工具 =======
    const IOTools = {
        // 导出JSON备份
        exportJSON() {
            const fileName = `prompt_master_${currentMode}_${new Date().toISOString().slice(0,10)}.json`;
            const blob = new Blob([JSON.stringify(promptData, null, 2)], { type: "application/json" });
            const url = URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.href = url;
            a.download = fileName;
            document.body.appendChild(a);
            a.click();
            document.body.removeChild(a);
            toast("✅ 已导出备份");
        },
        // 增量导入JSON
        importJSON() {
            const input = document.createElement('input');
            input.type = 'file';
            input.accept = '.json';
            input.style.display = 'none';
            document.body.appendChild(input);

            input.onchange = (e) => {
                const file = e.target.files[0];
                if (!file) return;

                const reader = new FileReader();
                reader.onload = (event) => {
                    try {
                        const newData = JSON.parse(event.target.result);
                        if (typeof newData === 'object' && newData !== null) {
                            let addedCount = 0;
                            Object.keys(newData).forEach(cat => {
                                if (!promptData[cat]) {
                                    promptData[cat] = newData[cat];
                                    addedCount += newData[cat].length;
                                } else {
                                    const existingNames = new Set(promptData[cat].map(item => item.name));
                                    newData[cat].forEach(newItem => {
                                        if (!existingNames.has(newItem.name)) {
                                            promptData[cat].push(newItem);
                                            addedCount++;
                                        }
                                    });
                                }
                            });
                            if (addedCount > 0) {
                                DataManager.save();
                                alert(`✅ 增量导入成功!\n共新增 ${addedCount} 条模板。`);
                            } else {
                                alert("⚠️ 导入完成,没有新增内容。");
                            }
                        } else {
                            toast("❌ 导入格式错误");
                        }
                    } catch (err) {
                        toast("❌ 导入解析失败");
                    }
                    document.body.removeChild(input);
                };
                reader.readAsText(file);
            };
            input.click();
        }
    };

    // ======= 3. 样式表 (状态栏增强 + 半透明预览 + 自适应布局) =======
    GM_addStyle(`
        #pm-root { font-family: -apple-system, system-ui, sans-serif; }

        /* 主面板样式 - 改为自适应宽度 */
        .pm-panel {
            position: fixed;
            top: 80px;
            right: 20px;
            width: auto;           /* 允许宽度自动 */
            min-width: 260px;      /* 设置一个稍微宽一点的最小值 */
            max-width: 350px;      /* 防止过宽 */
            background: #fff;
            border-radius: 12px;
            box-shadow: 0 8px 32px rgba(0,0,0,0.1);
            z-index: 2147483647;
            border: 1px solid #eee;
            display: flex;
            flex-direction: column;
            transition: width 0.2s ease; /* 平滑过渡 */
        }

        /* 折叠悬浮球 */
        .pm-float-ball {
            position: fixed;
            top: 80px;
            right: 20px;
            width: 40px;
            height: 40px;
            background: #1a73e8;
            border-radius: 50%;
            box-shadow: 0 4px 12px rgba(26,115,232,0.3);
            z-index: 2147483647;
            cursor: pointer;
            display: flex;
            justify-content: center;
            align-items: center;
            color: #fff;
            font-weight: bold;
            font-size: 14px;
            border: 2px solid #fff;
            transition: transform 0.2s, background 0.2s;
            user-select: none;
        }
        .pm-float-ball:hover {
            transform: scale(1.1);
            background: #1557b0;
        }

        /* 标题栏布局 - 增加间距和防挤压 */
        .pm-header {
            padding: 10px 12px;
            background: #fcfcfc;
            border-bottom: 1px solid #f0f0f0;
            display: flex;
            justify-content: space-between;
            align-items: center;
            border-radius: 12px 12px 0 0;
            flex-wrap: wrap; /* 允许必要时换行 */
            gap: 8px; /* 元素间增加间距 */
        }
        .pm-title-area {
            display: flex;
            align-items: center;
            gap: 6px; /* 增加标题和左侧徽章的间距 */
            flex-wrap: nowrap;
            flex: 1 1 auto;
        }
        .pm-title {
            font-size: 14px; /* 稍微加大标题字号 */
            font-weight: 700;
            color: #1a73e8;
            white-space: nowrap;
        }

        /* 状态徽章 */
        .pm-badge {
            font-size: 11px; /* 优化字体大小 */
            padding: 3px 6px;
            border-radius: 4px;
            cursor: pointer;
            display: flex;
            align-items: center;
            transition: 0.2s;
            border: 1px solid transparent;
            user-select: none;
            white-space: nowrap; /* 防止徽章文字换行 */
            line-height: 1.2;
        }
        .pm-badge:hover {
            filter: brightness(0.95);
            transform: translateY(-1px);
        }

        /* 存储模式配色 */
        .mode-local { background: #e6f4ea; color: #137333; border-color: #ceead6; }
        .mode-cloud { background: #e8f0fe; color: #1967d2; border-color: #d2e3fc; }

        /* 填充模式配色 */
        .fill-append { background: #f5f9ff; color: #406599; border-color: #e1eafc; }
        .fill-replace { background: #f8f0f5; color: #8b5cf6; border-color: #f3e8ff; }

        /* 标题栏右侧按钮区 */
        .pm-header-right {
            display: flex;
            align-items: center;
            gap: 4px; /* 按钮间距 */
            flex: 0 0 auto; /* 保持不被压缩 */
        }
        .pm-icon-btn {
            padding: 5px;
            border-radius: 4px;
            cursor: pointer;
            font-size: 14px;
            display: flex;
            align-items: center;
            justify-content: center;
            transition: 0.2s;
            color: #5f6368;
        }
        .pm-icon-btn:hover {
            background: #f1f3f4;
            color: #1a73e8;
        }
        .pm-icon-active {
            color: #1a73e8;
            font-weight:bold;
            background: #e8f0fe;
        }

        /* 内容区域 */
        .pm-body {
            padding: 8px;
            max-height: 60vh;
            overflow-y: auto;
            scrollbar-width: thin;
            position: relative;
            min-height: 100px;
        }

        /* 底部工具栏 */
        .pm-footer {
            padding: 8px;
            border-top: 1px solid #eee;
            display: flex;
            gap: 6px;
            background: #fff;
            border-radius: 0 0 12px 12px;
        }
        .pm-tool-btn {
            flex: 1;
            padding: 6px;
            border: 1px solid #f1f3f4;
            background: #f8f9fa;
            border-radius: 6px;
            font-size: 11px;
            cursor: pointer;
            color: #5f6368;
            text-align: center;
            transition:0.2s;
            white-space: nowrap;
        }
        .pm-tool-btn:hover {
            background: #e8f0fe;
            color: #1967d2;
            border-color: #d2e3fc;
        }

        /* 分类与模板列表 */
        .pm-cat-wrap {
            margin-bottom: 8px;
            border-radius: 8px;
            overflow: hidden;
        }
        .pm-cat-header {
            display: flex;
            align-items: center;
            padding: 6px 4px;
            background: #f8f9fa;
            cursor: pointer;
            position: relative;
        }
        .pm-cat-fold-icon {
            font-size: 10px;
            margin-right: 6px;
            transition: transform 0.2s;
            color: #70757a;
        }
        .pm-cat-name {
            font-size: 12px; /* 字体微调 */
            color: #5f6368;
            font-weight: 700;
            flex: 1;
            text-transform: uppercase;
            letter-spacing: 0.5px;
        }
        .pm-cat-tools {
            display: none;
            gap: 6px;
            margin-right: 4px;
        }
        .pm-cat-header:hover .pm-cat-tools {
            display: flex;
        }
        .pm-tpl-list {
            padding: 4px 0;
        }
        .pm-tpl-list.folded {
            display: none;
        }
        .pm-item-wrap {
            position: relative;
            margin-bottom: 2px;
        }
        .pm-btn {
            width: 100%;
            border: none;
            background: transparent;
            padding: 8px 10px;
            text-align: left;
            font-size: 13px; /* 模板字体加大 */
            border-radius: 6px;
            cursor: pointer;
            color: #3c4043;
            transition: 0.2s;
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
        }
        .pm-btn:hover {
            background: #f1f3f4;
            color: #1a73e8;
        }

        /* 半透明预览浮窗 */
        #pm-preview-float {
            position: fixed;
            display: none;
            width: auto;
            max-width: 280px;
            background: rgba(255, 255, 255, 0.85);
            backdrop-filter: blur(8px);
            border: 1px solid rgba(0,0,0,0.08);
            box-shadow: 0 4px 16px rgba(0,0,0,0.1);
            border-radius: 8px;
            padding: 10px;
            font-size: 12px;
            line-height: 1.5;
            color: #444;
            z-index: 2147483647;
            pointer-events: auto;
            word-break: break-all;
            transition: opacity 0.2s;
            opacity: 0;
        }
        #pm-preview-float.show {
            opacity: 1; /* 显示时不透明 */
        }

        /* 原地编辑器 */
        .pm-inline-editor {
            background: #fff;
            border: 1px solid #1a73e8;
            border-radius: 8px;
            padding: 8px;
            margin: 4px 0;
            box-shadow: 0 4px 12px rgba(26,115,232,0.15);
        }
        .pm-inline-editor textarea {
            width: 100%;
            height: 100px;
            border: 1px solid #dadce0;
            border-radius: 4px;
            padding: 6px;
            font-size: 12px;
            box-sizing: border-box;
            resize: vertical;
            margin: 5px 0;
        }
        .pm-inline-editor input, .pm-inline-editor select {
            width: 100%;
            border: 1px solid #dadce0;
            border-radius: 4px;
            padding: 4px 6px;
            font-size: 12px;
            box-sizing: border-box;
            margin-bottom: 4px;
        }
        .pm-ed-btns {
            display: flex;
            justify-content: flex-end;
            gap: 6px;
        }
        .pm-ebtn {
            padding: 3px 8px;
            font-size: 11px;
            border-radius: 4px;
            cursor: pointer;
            border: none;
        }
        .pm-save {
            background: #1a73e8;
            color: #fff;
        }
        .pm-cancel {
            background: #f1f3f4;
            color: #5f6368;
        }

        /* 加载中遮罩 */
        .pm-loading {
            position: absolute;
            top:0;
            left:0;
            width:100%;
            height:100%;
            background:rgba(255,255,255,0.8);
            display:flex;
            justify-content:center;
            align-items:center;
            font-size:12px;
            color:#666;
            z-index:10;
        }

        /* 提示弹窗 */
        .pm-toast {
            position: fixed;
            bottom: 30px;
            left: 50%;
            transform: translateX(-50%);
            background: #323232;
            color: #fff;
            padding: 6px 16px;
            border-radius: 16px;
            font-size: 12px;
            z-index: 2147483647;
            display: none;
        }
    `);

    // ======= 4. 稳定填充核心 =======
    async function stableInject(text) {
        // 兼容多平台输入框选择器
        const inputField = document.querySelector(
            'div[role="textbox"], #prompt-textarea, textarea[placeholder*="输入"], #chat-input, [contenteditable="true"], textarea'
        );
        if (!inputField) return toast("❌ 未找到输入框");

        inputField.focus();
        const isRich = inputField.isContentEditable;
        const oldVal = isRich ? inputField.innerText : inputField.value;
        const newVal = (appendMode && oldVal.trim()) ? (oldVal + "\n" + text) : text;

        try {
            if (isRich) {
                // 富文本输入框处理
                const selection = window.getSelection();
                const range = document.createRange();
                range.selectNodeContents(inputField);
                selection.removeAllRanges();
                selection.addRange(range);
                document.execCommand('delete', false);
                document.execCommand('insertText', false, newVal);
            } else {
                // 普通文本框处理(绕过输入限制)
                const setter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, "value").set;
                setter.call(inputField, newVal);
                inputField.dispatchEvent(new Event('input', { bubbles: true }));
            }
            toast("✅ 填充成功");
            inputField.focus();
            // 滚动到输入框底部
            setTimeout(() => { inputField.scrollTop = inputField.scrollHeight; }, 10);
        } catch (e) {
            toast("❌ 尝试填充失败");
        }
    }

    // ======= 5. UI 渲染引擎 =======
    function toast(msg, time=2000) {
        const t = document.getElementById('pm-toast') || (() => {
            const d = document.createElement('div');
            d.id = 'pm-toast';
            d.className = 'pm-toast';
            document.body.appendChild(d);
            return d;
        })();
        setHTML(t, msg);
        t.style.display = 'block';
        setTimeout(() => t.style.display = 'none', time);
    }

    function renderUI() {
        if (!document.body) return;

        // 创建根容器和预览浮窗
        let root = document.getElementById('pm-root');
        if (!root) {
            root = document.createElement('div');
            root.id = 'pm-root';
            document.body.appendChild(root);

            const pf = document.createElement('div');
            pf.id = 'pm-preview-float';
            document.body.appendChild(pf);
        }
        const previewFloat = document.getElementById('pm-preview-float');

        // 如果处于折叠状态,只渲染悬浮球
        if (isMinimized) {
            setHTML(root, `<div class="pm-float-ball" id="pm-restore-btn" title="点击展开">AI</div>`);
            document.getElementById('pm-restore-btn').onclick = () => {
                isMinimized = false;
                GM_setValue(MINIMIZED_KEY, false);
                renderUI();
            };
            return;
        }

        // 样式逻辑(动态生成状态徽章类名/文本)
        const modeClass = currentMode === 'local' ? 'mode-local' : 'mode-cloud';
        const modeText = currentMode === 'local' ? '🏠 本地' : '☁️ 云端';
        const fillClass = appendMode ? 'fill-append' : 'fill-replace';
        const fillText = appendMode ? '➕ 追加' : '🔄 替换';
        const configIcon = isEditMode ? '✅' : '⚙️';

        // 渲染主面板
        setHTML(root, `
            <div class="pm-panel">
                <div class="pm-header">
                    <div class="pm-title-area">
                        <span class="pm-title">提示词大师</span>
                        <span id="pm-switch-storage" class="pm-badge ${modeClass}" title="切换存储模式">${modeText}</span>
                    </div>
                    <div class="pm-header-right">
                        <span id="pm-switch-fill" class="pm-badge ${fillClass}" title="切换填充模式">${fillText}</span>
                        <span id="pm-config-btn" class="pm-icon-btn ${isEditMode?'pm-icon-active':''}" title="编辑管理">${configIcon}</span>
                        <span id="pm-fold-btn" class="pm-icon-btn" title="折叠面板" style="font-weight:bold; margin-left:2px;">−</span>
                    </div>
                </div>
                <div class="pm-body" id="pm-list-container">
                    ${isLoading ? '<div class="pm-loading">同步中...</div>' : ''}
                </div>
                <div class="pm-footer">
                    <button class="pm-tool-btn" id="pm-export-btn">📤 导出</button>
                    <button class="pm-tool-btn" id="pm-import-btn">📥 导入</button>
                    ${isEditMode ? `<button class="pm-tool-btn" id="pm-new-cat" style="background:#e8f0fe; color:#1a73e8;">+ 分类</button>` : ''}
                </div>
            </div>
        `);

        // 绑定折叠按钮事件
        document.getElementById('pm-fold-btn').onclick = () => {
            isMinimized = true;
            GM_setValue(MINIMIZED_KEY, true);
            renderUI();
        };

        if (isLoading) return;

        // 渲染分类和模板列表
        const container = document.getElementById('pm-list-container');
        Object.keys(promptData).forEach(cat => {
            const isFolded = foldedCats.includes(cat);
            const catWrap = document.createElement('div');
            catWrap.className = 'pm-cat-wrap';

            // 分类头部(折叠/编辑)
            const header = document.createElement('div');
            header.className = 'pm-cat-header';
            setHTML(header, `
                <span class="pm-cat-fold-icon" style="transform: ${isFolded ? 'rotate(-90deg)' : 'rotate(0deg)'}">▼</span>
                <span class="pm-cat-name">${cat}</span>
                ${isEditMode ? `<div class="pm-cat-tools">
                    <span class="pm-ed-cat" data-cat="${cat}">✏️</span>
                    <span class="pm-del-cat" data-cat="${cat}">×</span>
                </div>` : ''}
            `);

            // 分类折叠/展开逻辑
            header.onclick = (e) => {
                if (e.target.closest('.pm-cat-tools')) return;
                if (isFolded) {
                    foldedCats = foldedCats.filter(c => c !== cat);
                } else {
                    foldedCats.push(cat);
                }
                GM_setValue(FOLD_KEY, foldedCats);
                renderUI();
            };

            // 分类编辑/删除事件
            if (isEditMode) {
                header.querySelector('.pm-ed-cat').onclick = () => editCatName(catWrap, cat);
                header.querySelector('.pm-del-cat').onclick = () => deleteCat(cat);
            }
            catWrap.appendChild(header);

            // 模板列表
            const tplList = document.createElement('div');
            tplList.className = `pm-tpl-list ${isFolded ? 'folded' : ''}`;

            // 渲染单个模板
            promptData[cat].forEach((item, idx) => {
                const itemWrap = document.createElement('div');
                itemWrap.className = 'pm-item-wrap';
                const btn = document.createElement('button');
                btn.className = 'pm-btn';
                btn.innerText = item.name;

                // 预览浮窗逻辑
                btn.onmouseenter = (e) => {
                    if (isEditMode) return;
                    const rect = btn.getBoundingClientRect();
                    // 显示模板名称 + 150字预览
                    const nameText = `<div style="font-weight: 600; color: #1a73e8; margin-bottom: 6px; border-bottom: 1px solid rgba(0,0,0,0.05); padding-bottom: 4px;">${item.name}</div>`;
                    const coreText = item.content.length > 150 ? item.content.substring(0, 150) + "..." : item.content;
                    setHTML(previewFloat, `${nameText}${coreText.replace(/\n/g, '<br>')}`);

                    // 浮窗显示+动画
                    previewFloat.style.display = 'block';
                    previewFloat.classList.add('show');

                    // 优化浮窗位置(避免出界)
                    let topPos = rect.top + window.scrollY;
                    let leftPos = rect.right + 15;
                    // 右侧出界则显示在左侧
                    if (leftPos + previewFloat.offsetWidth > window.innerWidth) {
                        leftPos = rect.left - previewFloat.offsetWidth - 15;
                    }
                    // 底部出界则向上调整
                    if (topPos + previewFloat.offsetHeight > window.innerHeight + window.scrollY) {
                        topPos = rect.bottom - previewFloat.offsetHeight + window.scrollY;
                    }

                    previewFloat.style.top = `${topPos}px`;
                    previewFloat.style.left = `${leftPos}px`;
                    previewFloat.style.right = 'auto';
                };

                // 鼠标离开隐藏浮窗(带过渡动画)
                btn.onmouseleave = () => {
                    previewFloat.classList.remove('show');
                    setTimeout(() => {
                        previewFloat.style.display = 'none';
                    }, 150);
                };

                // 模板点击事件(编辑/填充)
                btn.onclick = () => {
                    if (isEditMode) {
                        editTpl(itemWrap, cat, idx);
                    } else {
                        stableInject(item.content);
                        previewFloat.style.display = 'none';
                    }
                };
                itemWrap.appendChild(btn);
                tplList.appendChild(itemWrap);
            });

            // 编辑模式下添加新模板按钮
            if (isEditMode) {
                const addTplBtn = document.createElement('button');
                addTplBtn.className = 'pm-btn';
                addTplBtn.style.border = "1px dashed #ccc";
                addTplBtn.innerText = "+ 新模板";
                addTplBtn.onclick = () => editTpl(tplList, cat, -1, addTplBtn);
                tplList.appendChild(addTplBtn);
            }
            catWrap.appendChild(tplList);
            container.appendChild(catWrap);
        });

        // 事件绑定
        // 编辑模式切换
        document.getElementById('pm-config-btn').onclick = () => {
            isEditMode = !isEditMode;
            renderUI();
        };

        // 填充模式切换(持久化)
        document.getElementById('pm-switch-fill').onclick = () => {
            appendMode = !appendMode;
            GM_setValue('pm_append_mode', appendMode);
            renderUI();
        };

        // 存储模式切换
        document.getElementById('pm-switch-storage').onclick = async () => {
            if (isLoading) return;
            const targetMode = currentMode === 'local' ? '☁️ 云端' : '🏠 本地';
            if (confirm(`确认切换至 [${targetMode}] 模式?`)) {
                currentMode = currentMode === 'local' ? 'cloud' : 'local';
                GM_setValue(MODE_KEY, currentMode);
                await DataManager.load();
            }
        };

        // 导入/导出事件
        document.getElementById('pm-export-btn').onclick = IOTools.exportJSON;
        document.getElementById('pm-import-btn').onclick = IOTools.importJSON;

        // 新建分类事件
        if (isEditMode) {
            document.getElementById('pm-new-cat').onclick = () => {
                const n = prompt("请输入新分类名称:");
                if (n) {
                    promptData[n] = [];
                    DataManager.save();
                }
            };
        }
    }

    // ======= 6. 辅助编辑逻辑 =======
    // 编辑分类名称
    function editCatName(wrap, oldName) {
        const header = wrap.querySelector('.pm-cat-header');
        header.style.display = 'none';

        const editor = document.createElement('div');
        editor.className = 'pm-inline-editor';
        setHTML(editor, `
            <input type="text" id="new-cat-inp" value="${oldName}">
            <div class="pm-ed-btns">
                <button class="pm-ebtn pm-cancel">取消</button>
                <button class="pm-ebtn pm-save">保存</button>
            </div>
        `);
        wrap.prepend(editor);

        editor.querySelector('.pm-cancel').onclick = () => renderUI();
        editor.querySelector('.pm-save').onclick = () => {
            const n = editor.querySelector('#new-cat-inp').value.trim();
            if (n && n !== oldName) {
                promptData[n] = promptData[oldName];
                delete promptData[oldName];
                DataManager.save();
            } else {
                renderUI();
            }
        };
    }

    // 删除分类
    function deleteCat(catName) {
        if (!confirm(`确定删除分类 [${catName}] 吗?删除后该分类下的所有模板也会被移除。`)) return;
        delete promptData[catName];
        DataManager.save();
    }

    // 编辑/新增模板
    function editTpl(container, cat, idx, addBtn = null) {
        const isNew = idx === -1;
        const item = isNew ? { name: "", content: "" } : promptData[cat][idx];

        const editor = document.createElement('div');
        editor.className = 'pm-inline-editor';
        // 生成分类下拉选项
        let cats = Object.keys(promptData).map(c =>
            `<option value="${c}" ${c === cat ? 'selected' : ''}>${c}</option>`
        ).join('');

        setHTML(editor, `
            <input type="text" id="ed-name" placeholder="模板名称" value="${item.name}">
            <select id="ed-cat">${cats}</select>
            <div style="position:relative">
                <textarea id="ed-cont" placeholder="提示词内容...">${item.content}</textarea>
            </div>
            <div class="pm-ed-btns">
                <button class="pm-ebtn pm-cancel">取消</button>
                <button class="pm-ebtn pm-save">保存</button>
            </div>
        `);

        // 隐藏原模板按钮/新增按钮
        if (!isNew) container.querySelector('.pm-btn').style.display = 'none';
        if (addBtn) addBtn.style.display = 'none';

        container.appendChild(editor);

        // 取消编辑
        editor.querySelector('.pm-cancel').onclick = () => renderUI();
        // 保存模板
        editor.querySelector('.pm-save').onclick = () => {
            const n = editor.querySelector('#ed-name').value.trim();
            const c = editor.querySelector('#ed-cont').value;
            const tCat = editor.querySelector('#ed-cat').value;

            if (!n || !c) {
                toast("❌ 名称和内容不能为空");
                return;
            }

            // 编辑模式:删除原模板
            if (!isNew) promptData[cat].splice(idx, 1);
            // 添加新模板
            promptData[tCat].push({ name: n, content: c });
            DataManager.save();
        };
    }

    // ======= 7. 启动初始化 =======
    const init = async () => {
        if (!document.getElementById('pm-root')) {
            renderUI();
            await DataManager.load();
        }
    };

    // 监听页面加载,确保UI正确渲染
    const observer = new MutationObserver(() => {
        if (!document.getElementById('pm-root')) renderUI();
    });

    const startObserver = () => {
        if (document.body) {
            observer.observe(document.body, { childList: true, subtree: false });
            init();
        } else {
            setTimeout(startObserver, 100);
        }
    };
    startObserver();

    // 兜底检查(防止Observer失效)
    setInterval(() => {
        if (document.body && !document.getElementById('pm-root')) renderUI();
    }, 2000);

})();