公开 版本 1.0

最新pplx提示词脚本

kuma
kuma
11 次浏览
2025-11-13
描述

youhou

提示词内容
// ==UserScript==
// @name         Gemini / Genspark / Perplexity 提示词管理器
// @namespace    http://tampermonkey.net/
// @version      2.2.0
// @description  为 Gemini、Genspark 与 Perplexity 添加提示词管理功能,支持增删改查和快速插入
// @author       Your Name
// @match        https://gemini.google.com/*
// @match        https://www.genspark.ai/agents*
// @match        https://genspark.ai/agents*
// @match        https://www.perplexity.ai/*
// @match        https://perplexity.ai/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @run-at       document-idle
// ==/UserScript==

(function () {
  "use strict";

  // 防止重复初始化
  if (window.promptManagerInitialized) return;
  window.promptManagerInitialized = true;

  // 站点识别
  const isGemini = location.hostname.includes("gemini.google.com");
  const isGenspark = location.hostname.includes("genspark.ai");
  const isPerplexity = location.hostname.includes("perplexity.ai");

  // 主题
  const THEME = isGemini
    ? { g1: "#4285f4", g2: "#34a853", name: "Gemini" }
    : isGenspark
    ? { g1: "#667eea", g2: "#764ba2", name: "Genspark" }
    : isPerplexity
    ? { g1: "#0ea5e9", g2: "#8b5cf6", name: "Perplexity" }
    : { g1: "#3b82f6", g2: "#9333ea", name: "Site" };

  // 默认提示词
  const DEFAULT_PROMPTS = [
    {
      id: "default_1",
      title: "代码优化",
      content: "请帮我优化以下代码,提高性能和可读性:",
      category: "编程",
    },
    {
      id: "default_2",
      title: "翻译助手",
      content: "请将以下内容翻译成中文,保持专业术语的准确性:",
      category: "翻译",
    },
    {
      id: "default_3",
      title: "文章总结",
      content: "请帮我总结以下文章的要点,用简洁的语言概括主要内容:",
      category: "写作",
    },
    {
      id: "default_4",
      title: "创意写作",
      content: "请基于以下主题进行创意写作,要求生动有趣:",
      category: "写作",
    },
    {
      id: "default_5",
      title: "问题解答",
      content: "请详细解答以下问题,提供准确和全面的信息:",
      category: "学习",
    },
  ];

  // 安全创建元素
  function createElementSafely(tag, props = {}, text = "") {
    const el = document.createElement(tag);
    Object.keys(props).forEach((k) => {
      if (k === "className") el.className = props[k];
      else if (k === "style") el.setAttribute("style", props[k]);
      else el.setAttribute(k, props[k]);
    });
    if (text) el.textContent = text;
    return el;
  }
  function clearElementSafely(el) {
    while (el.firstChild) el.removeChild(el.firstChild);
  }

  class UniversalPromptManager {
    // 获取仍然挂在 DOM 上的当前输入框;若失效则重找
    getLiveEditorEl() {
      if (this.textarea && document.body.contains(this.textarea))
        return this.textarea;

      // 优先 Perplexity 的 #ask-input,其次兜底选择器
      const el =
        document.querySelector('#ask-input[contenteditable="true"]') ||
        document.querySelector(
          'div#ask-input[contenteditable="true"][data-lexical-editor="true"]'
        ) ||
        document.querySelector('div[contenteditable="true"][role="textbox"]') ||
        document.querySelector("textarea");

      if (el) this.textarea = el;
      return el;
    }
    constructor() {
      this.prompts = this.loadPrompts();
      this.selectedPrompt = null;
      this.textarea = null; // textarea 或 contenteditable
      this.isCollapsed = false;
      this.site = isGemini
        ? "gemini"
        : isGenspark
        ? "genspark"
        : isPerplexity
        ? "perplexity"
        : "unknown";
      this._pmObserver = null;
      this._pmRouteHooked = false;
      this._pmFocusHooked = false;
      this.init();
    }

    // 加载/保存
    loadPrompts() {
      const saved = GM_getValue("universal_prompts", null);
      if (!saved) {
        GM_setValue("universal_prompts", DEFAULT_PROMPTS);
        return DEFAULT_PROMPTS;
      }
      return saved;
    }
    savePrompts() {
      GM_setValue("universal_prompts", this.prompts);
    }

    // CRUD
    addPrompt(p) {
      p.id = "custom_" + Date.now();
      this.prompts.push(p);
      this.savePrompts();
      this.refreshPromptList();
    }
    updatePrompt(id, u) {
      const i = this.prompts.findIndex((p) => p.id === id);
      if (i !== -1) {
        this.prompts[i] = { ...this.prompts[i], ...u };
        this.savePrompts();
        this.refreshPromptList();
      }
    }
    deletePrompt(id) {
      this.prompts = this.prompts.filter((p) => p.id !== id);
      this.savePrompts();
      this.refreshPromptList();
    }
    getCategories() {
      return Array.from(
        new Set(this.prompts.map((p) => p.category).filter(Boolean))
      );
    }

    // 初始化
    init() {
      this.createStyles();
      this.createUI();
      this.bindEvents();
      this.findTextarea();
    }

    // 样式
    createStyles() {
      const old = document.getElementById("universal-prompt-manager-styles");
      if (old) old.remove();
      const style = document.createElement("style");
      style.id = "universal-prompt-manager-styles";
      style.textContent = `
        #universal-prompt-panel{position:fixed;top:50%;right:20px;transform:translateY(-50%);width:320px;max-height:70vh;background:#fff;border-radius:12px;box-shadow:0 10px 40px rgba(0,0,0,.15);z-index:999999;display:flex;flex-direction:column;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;transition:.3s;border:1px solid #e0e0e0}
        #universal-prompt-panel.collapsed{display:none}
        .prompt-panel-header{padding:16px;background:linear-gradient(135deg,${
          THEME.g1
        } 0%,${
        THEME.g2
      } 100%);color:#fff;border-radius:12px 12px 0 0;display:flex;justify-content:space-between;align-items:center;cursor:move}
        .prompt-panel-title{font-size:16px;font-weight:600;display:flex;align-items:center;gap:8px}
        .site-indicator{font-size:12px;padding:2px 6px;background:rgba(255,255,255,.2);border-radius:4px;margin-left:8px}
        .prompt-panel-controls{display:flex;gap:8px}
        .prompt-panel-btn{background:rgba(255,255,255,.2);border:none;color:#fff;width:28px;height:28px;border-radius:6px;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:.2s;font-size:14px}
        .prompt-panel-btn:hover{background:rgba(255,255,255,.3);transform:scale(1.1)}
        .prompt-search-bar{padding:12px;border-bottom:1px solid #e5e7eb;background:#f9fafb}
        .prompt-search-input{width:100%;padding:8px 12px;border:1px solid #d1d5db;border-radius:8px;font-size:14px;transition:.2s;box-sizing:border-box}
        .prompt-search-input:focus{outline:0;border-color:${
          THEME.g1
        };box-shadow:0 0 0 3px rgba(16,185,129,.1)}
        .prompt-categories{padding:8px 12px;display:flex;gap:6px;flex-wrap:wrap;background:#fff;border-bottom:1px solid #e5e7eb}
        .category-tag{padding:4px 10px;background:#f3f4f6;border-radius:12px;font-size:12px;color:#4b5563;cursor:pointer;transition:.2s;border:1px solid transparent}
        .category-tag:hover{background:#e5e7eb}
        .category-tag.active{background:${THEME.g1};color:#fff;border-color:${
        THEME.g1
      }}
        .prompt-list{flex:1;overflow-y:auto;padding:8px}
        .prompt-item{background:#fff;border:1px solid #e5e7eb;border-radius:8px;padding:12px;margin-bottom:8px;cursor:pointer;transition:.2s;position:relative}
        .prompt-item:hover{border-color:${
          THEME.g1
        };box-shadow:0 4px 12px rgba(0,0,0,.08);transform:translateY(-2px)}
        .prompt-item.selected{background:linear-gradient(135deg,rgba(232,240,254,.6) 0%,rgba(241,248,233,.6) 100%);border-color:${
          THEME.g1
        }}
        .prompt-item-header{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:8px}
        .prompt-item-title{font-weight:600;font-size:14px;color:#1f2937;flex:1}
        .prompt-item-category{font-size:11px;padding:2px 6px;background:#f3f4f6;border-radius:4px;color:#6b7280}
        .prompt-item-content{font-size:13px;color:#6b7280;line-height:1.4;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
        .prompt-item-actions{position:absolute;top:8px;right:8px;display:none;gap:4px}
        .prompt-item:hover .prompt-item-actions{display:flex}
        .prompt-action-btn{width:24px;height:24px;border:none;background:#fff;border-radius:4px;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:.2s;box-shadow:0 1px 3px rgba(0,0,0,.1);font-size:12px}
        .prompt-action-btn:hover{background:#f3f4f6;transform:scale(1.1)}
        .add-prompt-btn{margin:12px;padding:10px;background:linear-gradient(135deg,${
          THEME.g1
        } 0%,${
        THEME.g2
      } 100%);color:#fff;border:none;border-radius:8px;font-size:14px;font-weight:500;cursor:pointer;transition:.2s;display:flex;align-items:center;justify-content:center;gap:6px}
        .add-prompt-btn:hover{transform:translateY(-2px);box-shadow:0 4px 12px rgba(0,0,0,.15)}
        .prompt-modal{position:fixed;inset:0;background:rgba(0,0,0,.5);display:flex;align-items:center;justify-content:center;z-index:1000000;animation:fadeIn .2s}
        @keyframes fadeIn{from{opacity:0}to{opacity:1}}
        .prompt-modal-content{background:#fff;border-radius:12px;width:90%;max-width:500px;padding:24px;animation:slideUp .3s}
        @keyframes slideUp{from{transform:translateY(20px);opacity:0}to{transform:translateY(0);opacity:1}}
        .prompt-modal-header{font-size:18px;font-weight:600;margin-bottom:20px;color:#1f2937}
        .prompt-form-group{margin-bottom:16px}
        .prompt-form-label{display:block;font-size:14px;font-weight:500;color:#374151;margin-bottom:6px}
        .prompt-form-input,.prompt-form-textarea{width:100%;padding:8px 12px;border:1px solid #d1d5db;border-radius:6px;font-size:14px;transition:.2s;box-sizing:border-box}
        .prompt-form-textarea{min-height:100px;resize:vertical;font-family:inherit}
        .prompt-form-input:focus,.prompt-form-textarea:focus{outline:0;border-color:${
          THEME.g1
        };box-shadow:0 0 0 3px rgba(66,133,244,.1)}
        .prompt-modal-actions{display:flex;gap:12px;justify-content:flex-end;margin-top:24px}
        .prompt-modal-btn{padding:8px 16px;border-radius:6px;font-size:14px;font-weight:500;cursor:pointer;transition:.2s;border:none}
        .prompt-modal-btn.primary{background:linear-gradient(135deg,${
          THEME.g1
        } 0%,${THEME.g2} 100%);color:#fff}
        .prompt-modal-btn.secondary{background:#f3f4f6;color:#4b5563}
        .prompt-modal-btn:hover{transform:translateY(-1px);box-shadow:0 2px 8px rgba(0,0,0,.15)}
        .selected-prompt-bar{position:fixed;bottom:120px;left:50%;transform:translateX(-50%);background:linear-gradient(135deg,${
          THEME.g1
        } 0%,${
        THEME.g2
      } 100%);color:#fff;padding:8px 16px;border-radius:20px;font-size:13px;display:none;align-items:center;gap:8px;box-shadow:0 4px 12px rgba(0,0,0,.15);z-index:999998;animation:slideInUp .3s}
        @keyframes slideInUp{from{transform:translate(-50%,20px);opacity:0}to{transform:translate(-50%,0);opacity:1}}
        .selected-prompt-bar.show{display:flex}
        .selected-prompt-text{max-width:300px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
        .clear-prompt-btn{background:rgba(255,255,255,.2);border:none;color:#fff;width:20px;height:20px;border-radius:50%;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:.2s;font-size:14px}
        .clear-prompt-btn:hover{background:rgba(255,255,255,.3);transform:scale(1.1)}
        .prompt-list::-webkit-scrollbar{width:6px}.prompt-list::-webkit-scrollbar-track{background:#f3f4f6;border-radius:3px}.prompt-list::-webkit-scrollbar-thumb{background:#9ca3af;border-radius:3px}.prompt-list::-webkit-scrollbar-thumb:hover{background:#6b7280}
        .quick-prompt-btn{position:fixed;bottom:180px;right:30px;width:48px;height:48px;background:linear-gradient(135deg,${
          THEME.g1
        } 0%,${
        THEME.g2
      } 100%);border-radius:50%;display:flex;align-items:center;justify-content:center;color:#fff;font-size:20px;cursor:pointer;box-shadow:0 4px 12px rgba(0,0,0,.15);transition:.3s;z-index:999997;border:none}
        .quick-prompt-btn:hover{transform:scale(1.1);box-shadow:0 6px 20px rgba(0,0,0,.2)}
        .quick-prompt-btn.hidden{display:none}
        ${
          isGemini
            ? `
          .ql-editor{min-height:80px!important;max-height:200px!important;line-height:1.5!important;padding:12px!important;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif!important;font-size:14px!important}
        `
            : ""
        }
        ${
          isGenspark || isPerplexity
            ? `
          textarea{min-height:80px!important;max-height:200px!important;resize:vertical!important;line-height:1.5!important;padding:12px!important;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif!important;font-size:14px!important}
        `
            : ""
        }
      `;
      document.head.appendChild(style);
    }

    // UI
    createUI() {
      const ep = document.getElementById("universal-prompt-panel");
      const eb = document.querySelector(".selected-prompt-bar");
      const qb = document.querySelector(".quick-prompt-btn");
      if (ep) ep.remove();
      if (eb) eb.remove();
      if (qb) qb.remove();

      const panel = createElementSafely("div", {
        id: "universal-prompt-panel",
      });
      const header = createElementSafely("div", {
        className: "prompt-panel-header",
      });

      const title = createElementSafely("div", {
        className: "prompt-panel-title",
      });
      title.appendChild(createElementSafely("span", {}, "📝"));
      title.appendChild(createElementSafely("span", {}, "提示词管理"));
      title.appendChild(
        createElementSafely("span", { className: "site-indicator" }, THEME.name)
      );

      const controls = createElementSafely("div", {
        className: "prompt-panel-controls",
      });
      const refreshBtn = createElementSafely(
        "button",
        { className: "prompt-panel-btn", id: "refresh-prompts", title: "刷新" },
        "⟳"
      );
      const toggleBtn = createElementSafely(
        "button",
        { className: "prompt-panel-btn", id: "toggle-panel", title: "收起" },
        "−"
      );
      controls.appendChild(refreshBtn);
      controls.appendChild(toggleBtn);
      header.appendChild(title);
      header.appendChild(controls);

      const searchBar = createElementSafely("div", {
        className: "prompt-search-bar",
      });
      const searchInput = createElementSafely("input", {
        className: "prompt-search-input",
        id: "prompt-search",
        type: "text",
        placeholder: "搜索提示词...",
      });
      searchBar.appendChild(searchInput);

      const categories = createElementSafely("div", {
        className: "prompt-categories",
        id: "prompt-categories",
      });
      const list = createElementSafely("div", {
        className: "prompt-list",
        id: "prompt-list",
      });

      const addBtn = createElementSafely("button", {
        className: "add-prompt-btn",
        id: "add-prompt",
      });
      addBtn.appendChild(createElementSafely("span", {}, "+"));
      addBtn.appendChild(createElementSafely("span", {}, "添加新提示词"));

      panel.appendChild(header);
      panel.appendChild(searchBar);
      panel.appendChild(categories);
      panel.appendChild(list);
      panel.appendChild(addBtn);
      document.body.appendChild(panel);

      const selectedBar = createElementSafely("div", {
        className: "selected-prompt-bar",
      });
      selectedBar.appendChild(createElementSafely("span", {}, "当前提示词:"));
      selectedBar.appendChild(
        createElementSafely("span", {
          className: "selected-prompt-text",
          id: "selected-prompt-text",
        })
      );
      const clearBtn = createElementSafely(
        "button",
        { className: "clear-prompt-btn", id: "clear-prompt" },
        "×"
      );
      selectedBar.appendChild(clearBtn);
      document.body.appendChild(selectedBar);

      const quickBtn = createElementSafely(
        "button",
        {
          className: "quick-prompt-btn",
          title: `打开提示词管理器 (${THEME.name})`,
        },
        "📝"
      );
      quickBtn.addEventListener("click", () => this.togglePanel());
      document.body.appendChild(quickBtn);

      this.refreshCategories();
      this.refreshPromptList();
    }

    togglePanel() {
      const panel = document.getElementById("universal-prompt-panel");
      const quickBtn = document.querySelector(".quick-prompt-btn");
      const toggleBtn = document.getElementById("toggle-panel");
      this.isCollapsed = !this.isCollapsed;
      if (this.isCollapsed) {
        panel.classList.add("collapsed");
        quickBtn.classList.remove("hidden");
        if (toggleBtn) toggleBtn.textContent = "+";
      } else {
        panel.classList.remove("collapsed");
        quickBtn.classList.add("hidden");
        if (toggleBtn) toggleBtn.textContent = "−";
      }
    }

    refreshCategories() {
      const container = document.getElementById("prompt-categories");
      if (!container) return;
      clearElementSafely(container);
      const allTag = createElementSafely(
        "span",
        { className: "category-tag active", "data-category": "all" },
        "全部"
      );
      container.appendChild(allTag);
      this.getCategories().forEach((cat) =>
        container.appendChild(
          createElementSafely(
            "span",
            { className: "category-tag", "data-category": cat },
            cat
          )
        )
      );
    }

    refreshPromptList(filter = "") {
      const container = document.getElementById("prompt-list");
      if (!container) return;
      const activeCategory =
        document.querySelector(".category-tag.active")?.dataset.category ||
        "all";
      let arr = this.prompts.slice();
      if (activeCategory !== "all")
        arr = arr.filter((p) => p.category === activeCategory);
      if (filter)
        arr = arr.filter(
          (p) =>
            p.title.toLowerCase().includes(filter.toLowerCase()) ||
            p.content.toLowerCase().includes(filter.toLowerCase())
        );
      clearElementSafely(container);

      if (!arr.length) {
        container.appendChild(
          createElementSafely(
            "div",
            { style: "text-align:center;padding:20px;color:#9ca3af;" },
            "暂无提示词"
          )
        );
        return;
      }

      arr.forEach((prompt) => {
        const item = createElementSafely("div", { className: "prompt-item" });
        if (this.selectedPrompt?.id === prompt.id)
          item.classList.add("selected");
        const head = createElementSafely("div", {
          className: "prompt-item-header",
        });
        head.appendChild(
          createElementSafely(
            "div",
            { className: "prompt-item-title" },
            prompt.title
          )
        );
        head.appendChild(
          createElementSafely(
            "span",
            { className: "prompt-item-category" },
            prompt.category || "未分类"
          )
        );
        const content = createElementSafely(
          "div",
          { className: "prompt-item-content" },
          prompt.content
        );

        const actions = createElementSafely("div", {
          className: "prompt-item-actions",
        });
        const editBtn = createElementSafely(
          "button",
          {
            className: "prompt-action-btn edit-prompt",
            "data-id": prompt.id,
            title: "编辑",
          },
          "✏"
        );
        const delBtn = createElementSafely(
          "button",
          {
            className: "prompt-action-btn delete-prompt",
            "data-id": prompt.id,
            title: "删除",
          },
          "🗑"
        );
        actions.appendChild(editBtn);
        actions.appendChild(delBtn);

        item.appendChild(head);
        item.appendChild(content);
        item.appendChild(actions);
        item.addEventListener("click", (e) => {
          if (!e.target.closest(".prompt-item-actions"))
            this.selectPrompt(prompt, item);
        });
        container.appendChild(item);
      });
    }

    selectPrompt(prompt, el) {
      this.selectedPrompt = prompt;
      document
        .querySelectorAll(".prompt-item")
        .forEach((i) => i.classList.remove("selected"));
      el.classList.add("selected");
      const bar = document.querySelector(".selected-prompt-bar");
      const txt = document.getElementById("selected-prompt-text");
      if (bar && txt) {
        txt.textContent = prompt.title;
        bar.classList.add("show");
      }
      this.insertPromptToTextarea(prompt.content);
      this.showToast(`已选择提示词: ${prompt.title}`);
    }

    insertPromptToTextarea(text) {
      if (!this.textarea) this.findTextarea();
      const el = this.textarea;
      if (!el) return this.showToast("未找到输入框,请刷新页面重试");

      if (isGemini) return this.insertToGemini(text);
      if (isGenspark) return this.insertAsTextarea(text);
      if (isPerplexity) return this.insertToPerplexity(text);
      // 兜底
      if (el.tagName && el.tagName.toLowerCase() === "textarea")
        return this.insertAsTextarea(text);
      return this.insertAsContentEditable(text);
    }

    // Gemini(contenteditable)
    insertToGemini(promptContent) {
      const editor = this.textarea;
      const current = (editor.textContent || "").trim();
      const newContent = !current
        ? promptContent + "\n\n"
        : promptContent + "\n\n" + current;
      editor.textContent = "";
      editor.focus();
      setTimeout(() => {
        editor.textContent = newContent;
        editor.dispatchEvent(new Event("input", { bubbles: true }));
        try {
          const range = document.createRange(),
            sel = getSelection();
          if (editor.firstChild) {
            range.setStart(editor.firstChild, promptContent.length + 2);
            range.collapse(true);
            sel.removeAllRanges();
            sel.addRange(range);
          }
        } catch {}
        editor.focus();
      }, 50);
    }

    // Perplexity(contenteditable: #ask-input)——像用户输入一样插入
    insertToPerplexity(promptContent) {
      const el = this.getLiveEditorEl();
      if (!el) {
        this.showToast("未找到输入框");
        return;
      }
      const text = promptContent + "\n\n";

      // textarea 直接用通用逻辑
      if (el.tagName && el.tagName.toLowerCase() === "textarea")
        return this.insertAsTextarea(promptContent);

      try {
        el.focus();
        const sel = window.getSelection();
        const range = document.createRange();
        range.selectNodeContents(el);
        range.collapse(true); // 开头插入;如需结尾,改为 false
        sel.removeAllRanges();
        sel.addRange(range);

        let ok = false;
        if (document.execCommand)
          ok = document.execCommand("insertText", false, text);
        if (!ok) el.textContent = text + (el.textContent || "");

        // 通知框架
        el.dispatchEvent(new Event("input", { bubbles: true }));
        el.dispatchEvent(new Event("change", { bubbles: true }));
      } catch (e) {
        // 最终兜底
        el.textContent = text + (el.textContent || "");
        el.dispatchEvent(new Event("input", { bubbles: true }));
      }
    }

    // 通用:任意 contenteditable 兜底
    insertAsContentEditable(promptContent) {
      const el = this.textarea;
      if (!el) return;
      const text = promptContent + "\n\n";
      try {
        el.focus();
        const sel = getSelection(),
          range = document.createRange();
        range.selectNodeContents(el);
        range.collapse(true);
        sel.removeAllRanges();
        sel.addRange(range);
        let ok = false;
        if (document.execCommand)
          ok = document.execCommand("insertText", false, text);
        if (!ok) el.textContent = text + (el.textContent || "");
        el.dispatchEvent(new Event("input", { bubbles: true }));
      } catch {
        el.textContent = text + (el.textContent || "");
        el.dispatchEvent(new Event("input", { bubbles: true }));
      }
    }

    // 通用:textarea
    insertAsTextarea(promptContent) {
      const ta = this.textarea;
      const current = (ta.value || "").trim();
      ta.value = !current
        ? promptContent + "\n\n"
        : promptContent + "\n\n" + current;
      this.adjustTextareaHeight();
      ta.dispatchEvent(new Event("input", { bubbles: true }));
      const pos = promptContent.length + 2;
      try {
        ta.setSelectionRange(pos, pos);
      } catch {}
      ta.focus();
    }

    adjustTextareaHeight() {
      const el = this.textarea;
      if (!el) return;
      const isTA = el.tagName && el.tagName.toLowerCase() === "textarea";
      if (isTA) {
        el.style.height = "auto";
        el.style.height = Math.min(el.scrollHeight, 200) + "px";
      }
    }

    clearSelectedPrompt() {
      this.selectedPrompt = null;
      document.querySelector(".selected-prompt-bar")?.classList.remove("show");
      document
        .querySelectorAll(".prompt-item")
        .forEach((i) => i.classList.remove("selected"));

      // 关键:拿到“仍然在页面上的”输入框
      const el = this.getLiveEditorEl();
      if (!el) {
        this.showToast("未找到输入框");
        return;
      }

      const isTA = el.tagName && el.tagName.toLowerCase() === "textarea";
      if (isTA) {
        el.value = "";
        this.adjustTextareaHeight();
        el.dispatchEvent(new Event("input", { bubbles: true }));
        el.dispatchEvent(new Event("change", { bubbles: true }));
        return;
      }

      // contenteditable(Perplexity / Lexical)
      const hardReset = () => {
        // Lexical 的空内容占位:一个空段落
        el.innerHTML = '<p dir="auto"><br></p>';
        el.dispatchEvent(new Event("input", { bubbles: true }));
        el.dispatchEvent(new Event("change", { bubbles: true }));
        // 放回光标到开头
        try {
          el.focus();
          const sel = getSelection(),
            r = document.createRange();
          r.selectNodeContents(el);
          r.collapse(true);
          sel.removeAllRanges();
          sel.addRange(r);
        } catch {}
      };

      try {
        el.focus();
        // 像用户一样:全选 → 删除
        const sel = getSelection();
        const r = document.createRange();
        r.selectNodeContents(el);
        sel.removeAllRanges();
        sel.addRange(r);

        // 先尝试 beforeinput/input(很多富文本靠这个驱动)
        const fire = (type, inputType) =>
          el.dispatchEvent(new InputEvent(type, { bubbles: true, inputType }));
        let acted =
          fire("beforeinput", "deleteContent") | fire("input", "deleteContent");

        // 再尝试 execCommand(很多编辑器仍兼容)
        if (document.execCommand) {
          acted = document.execCommand("delete", false, null) || acted;
        }

        // 若都没效果,做硬重置(最稳)
        if (!acted) hardReset();

        // 有些框架会立刻“回写”旧内容,这里再保险一次(50ms 后再清一遍)
        setTimeout(() => {
          if (el.textContent && el.textContent.trim() !== "") hardReset();
        }, 50);
      } catch {
        hardReset();
      }
    }

    showEditModal(prompt = null) {
      const isEdit = !!prompt;
      const modal = createElementSafely("div", { className: "prompt-modal" });
      const mc = createElementSafely("div", {
        className: "prompt-modal-content",
      });
      const mh = createElementSafely(
        "div",
        { className: "prompt-modal-header" },
        isEdit ? "编辑提示词" : "添加新提示词"
      );

      const g1 = createElementSafely("div", { className: "prompt-form-group" });
      g1.appendChild(
        createElementSafely("label", { className: "prompt-form-label" }, "标题")
      );
      const ti = createElementSafely("input", {
        className: "prompt-form-input",
        id: "prompt-title",
        type: "text",
        value: isEdit ? prompt.title : "",
      });
      g1.appendChild(ti);

      const g2 = createElementSafely("div", { className: "prompt-form-group" });
      g2.appendChild(
        createElementSafely("label", { className: "prompt-form-label" }, "分类")
      );
      const ci = createElementSafely("input", {
        className: "prompt-form-input",
        id: "prompt-category",
        type: "text",
        value: isEdit ? prompt.category || "" : "",
        placeholder: "例如:编程、翻译、写作",
      });
      g2.appendChild(ci);

      const g3 = createElementSafely("div", { className: "prompt-form-group" });
      g3.appendChild(
        createElementSafely(
          "label",
          { className: "prompt-form-label" },
          "提示词内容"
        )
      );
      const ta = createElementSafely("textarea", {
        className: "prompt-form-textarea",
        id: "prompt-content",
      });
      ta.value = isEdit ? prompt.content : "";
      g3.appendChild(ta);

      const acts = createElementSafely("div", {
        className: "prompt-modal-actions",
      });
      const cancel = createElementSafely(
        "button",
        { className: "prompt-modal-btn secondary", id: "cancel-modal" },
        "取消"
      );
      const save = createElementSafely(
        "button",
        { className: "prompt-modal-btn primary", id: "save-prompt" },
        isEdit ? "保存" : "添加"
      );
      acts.appendChild(cancel);
      acts.appendChild(save);

      mc.appendChild(mh);
      mc.appendChild(g1);
      mc.appendChild(g2);
      mc.appendChild(g3);
      mc.appendChild(acts);
      modal.appendChild(mc);
      document.body.appendChild(modal);

      cancel.addEventListener("click", () => modal.remove());
      save.addEventListener("click", () => {
        const title = ti.value.trim();
        const category = ci.value.trim();
        const content = ta.value.trim();
        if (!title || !content) return alert("请填写标题和内容");
        if (isEdit) {
          this.updatePrompt(prompt.id, { title, category, content });
          this.showToast("提示词已更新");
        } else {
          this.addPrompt({ title, category, content });
          this.showToast("提示词已添加");
        }
        this.refreshCategories();
        modal.remove();
      });
      modal.addEventListener("click", (e) => {
        if (e.target === modal) modal.remove();
      });
      const esc = (e) => {
        if (e.key === "Escape") {
          modal.remove();
          document.removeEventListener("keydown", esc);
        }
      };
      document.addEventListener("keydown", esc);
    }

    showToast(msg) {
      const t = createElementSafely("div", { className: "prompt-toast" }, msg);
      t.style.position = "fixed";
      t.style.top = "20px";
      t.style.left = "50%";
      t.style.transform = "translateX(-50%)";
      t.style.background = "#10b981";
      t.style.color = "#fff";
      t.style.padding = "12px 20px";
      t.style.borderRadius = "8px";
      t.style.fontSize = "14px";
      t.style.boxShadow = "0 4px 12px rgba(0,0,0,.15)";
      t.style.zIndex = 1000001;
      document.body.appendChild(t);
      setTimeout(() => {
        t.style.opacity = "0";
        setTimeout(() => t.remove(), 300);
      }, 2000);
    }

    // ——★ 关键:更稳健的输入框捕获(含 Perplexity:#ask-input)★——
    findTextarea() {
      const isInputLike = (el) => {
        if (!el) return false;
        const cs = window.getComputedStyle(el);
        const rect = el.getBoundingClientRect();
        const visible =
          el.offsetParent !== null &&
          cs.visibility !== "hidden" &&
          cs.display !== "none" &&
          rect.width > 0 &&
          rect.height > 0;
        const isTA = el.tagName && el.tagName.toLowerCase() === "textarea";
        const isCE =
          el.getAttribute && el.getAttribute("contenteditable") === "true";
        return visible && (isTA || isCE);
      };

      const selectorGroups = [
        // Perplexity 特异选择器(你页上就是它)
        '#ask-input[contenteditable="true"]',
        'div#ask-input[contenteditable="true"][data-lexical-editor="true"]',
        'div[contenteditable="true"][id="ask-input"][role="textbox"]',

        // 常规优先
        "form textarea",
        'textarea[placeholder*="Ask"]',
        'textarea[placeholder*="Message"]',
        'textarea[aria-label*="Ask"]',
        'textarea[aria-label*="Message"]',
        'textarea[data-testid*="search"]',
        "textarea",

        // ContentEditable 兜底
        '.ql-editor[contenteditable="true"]',
        "rich-textarea .ql-editor",
        ".text-input-field_textarea .ql-editor",
        'div[contenteditable="true"][data-lexical-editor="true"]',
        'div[contenteditable="true"][role="textbox"]',
        'div[contenteditable="true"]',
      ];

      const pickBest = (nodes) => {
        const viewH =
          window.innerHeight || document.documentElement.clientHeight;
        let best = null,
          score = -Infinity;
        nodes.forEach((el) => {
          if (!isInputLike(el)) return;
          const rect = el.getBoundingClientRect();
          const s = viewH - rect.top + rect.width * 0.001; // 越靠近底部越大
          if (s > score) {
            score = s;
            best = el;
          }
        });
        return best;
      };

      let candidates = [];
      for (const sel of selectorGroups)
        candidates = candidates.concat(
          Array.from(document.querySelectorAll(sel))
        );
      const target = pickBest(candidates);

      if (target) {
        this.textarea = target;
        console.log(`找到 ${this.site} 文本框:`, target);

        if (
          this.textarea.tagName &&
          this.textarea.tagName.toLowerCase() === "textarea"
        ) {
          this.textarea.addEventListener("input", () =>
            this.adjustTextareaHeight()
          );
          this.adjustTextareaHeight();
        }

        if (!this._pmObserver) {
          this._pmObserver = new MutationObserver(() => {
            if (!this.textarea || !document.body.contains(this.textarea)) {
              this.textarea = null;
              setTimeout(() => this.findTextarea(), 200);
            }
          });
          this._pmObserver.observe(document.body, {
            childList: true,
            subtree: true,
          });
        }
        return true;
      }

      // 路由变化重试
      if (!this._pmRouteHooked) {
        this._pmRouteHooked = true;
        const _push = history.pushState;
        history.pushState = function () {
          const r = _push.apply(this, arguments);
          setTimeout(() => dispatchEvent(new Event("pm-route-changed")), 100);
          return r;
        };
        addEventListener("popstate", () =>
          setTimeout(() => dispatchEvent(new Event("pm-route-changed")), 100)
        );
        addEventListener("pm-route-changed", () =>
          setTimeout(() => this.findTextarea(), 200)
        );
      }

      // 手动聚焦捕获
      if (!this._pmFocusHooked) {
        this._pmFocusHooked = true;
        document.addEventListener(
          "focusin",
          (e) => {
            const el = e.target;
            if (isInputLike(el)) {
              this.textarea = el;
              console.log("已通过用户聚焦捕获到输入框:", el);
              this.showToast("已捕获到输入框");
            }
          },
          true
        );
      }

      setTimeout(() => this.findTextarea(), 800);
      return false;
    }

    // 事件绑定
    bindEvents() {
      const searchInput = document.getElementById("prompt-search");
      if (searchInput)
        searchInput.addEventListener("input", (e) =>
          this.refreshPromptList(e.target.value)
        );

      const categories = document.getElementById("prompt-categories");
      if (categories)
        categories.addEventListener("click", (e) => {
          if (e.target.classList.contains("category-tag")) {
            document
              .querySelectorAll(".category-tag")
              .forEach((t) => t.classList.remove("active"));
            e.target.classList.add("active");
            const kw = document.getElementById("prompt-search")?.value || "";
            this.refreshPromptList(kw);
          }
        });

      const addBtn = document.getElementById("add-prompt");
      if (addBtn) addBtn.addEventListener("click", () => this.showEditModal());

      const list = document.getElementById("prompt-list");
      if (list)
        list.addEventListener("click", (e) => {
          if (e.target.classList.contains("edit-prompt")) {
            const id = e.target.dataset.id;
            const p = this.prompts.find((x) => x.id === id);
            if (p) this.showEditModal(p);
          } else if (e.target.classList.contains("delete-prompt")) {
            if (confirm("确定要删除这个提示词吗?")) {
              const id = e.target.dataset.id;
              this.deletePrompt(id);
              this.refreshCategories();
              this.showToast("提示词已删除");
            }
          }
        });

      const clearBtn = document.getElementById("clear-prompt");
      if (clearBtn)
        clearBtn.addEventListener("click", () => {
          this.clearSelectedPrompt();
          this.showToast("已清除选中的提示词");
        });

      const refreshBtn = document.getElementById("refresh-prompts");
      if (refreshBtn)
        refreshBtn.addEventListener("click", () => {
          this.refreshPromptList();
          this.findTextarea();
          this.showToast("列表已刷新");
        });

      const toggleBtn = document.getElementById("toggle-panel");
      if (toggleBtn)
        toggleBtn.addEventListener("click", () => this.togglePanel());

      this.makeDraggable();
    }

    // 可拖动
    makeDraggable() {
      const panel = document.getElementById("universal-prompt-panel");
      const header = panel?.querySelector(".prompt-panel-header");
      if (!panel || !header) return;
      let isDragging = false,
        currentX,
        currentY,
        initialX,
        initialY,
        xOffset = 0,
        yOffset = 0;

      const dragStart = (e) => {
        if (e.target.closest(".prompt-panel-controls")) return;
        initialX = e.clientX - xOffset;
        initialY = e.clientY - yOffset;
        if (e.target === header || e.target.closest(".prompt-panel-title"))
          isDragging = true;
      };
      const drag = (e) => {
        if (!isDragging) return;
        e.preventDefault();
        currentX = e.clientX - initialX;
        currentY = e.clientY - initialY;
        xOffset = currentX;
        yOffset = currentY;
        panel.style.transform = `translate(${currentX}px, ${currentY}px)`;
      };
      const dragEnd = () => {
        initialX = currentX;
        initialY = currentY;
        isDragging = false;
      };

      header.addEventListener("mousedown", dragStart);
      document.addEventListener("mousemove", drag);
      document.addEventListener("mouseup", dragEnd);
    }
  }

  // 初始化:等待关键容器出现
  function init() {
    const check = setInterval(() => {
      let containers = [];
      if (isGemini)
        containers = [
          ".ql-editor",
          "rich-textarea",
          ".text-input-field_textarea",
        ];
      else if (isGenspark)
        containers = [
          ".input-wrapper",
          ".prompt-input-wrapper",
          ".textarea-wrapper",
          'textarea[name="query"]',
        ];
      else if (isPerplexity)
        containers = [
          "#ask-input",
          "form textarea",
          'div[contenteditable="true"][role="textbox"]',
        ];
      else containers = ["textarea", 'div[contenteditable="true"]'];

      const found = containers.some((sel) => document.querySelector(sel));
      if (found) {
        clearInterval(check);
        console.log(`${THEME.name} 提示词管理器: 页面元素已加载,开始初始化`);
        try {
          new UniversalPromptManager();
        } catch (e) {
          console.error("提示词管理器: 初始化失败", e);
        }
      }
    }, 1000);
    setTimeout(() => clearInterval(check), 30000);
  }

  if (document.readyState === "loading")
    document.addEventListener("DOMContentLoaded", init);
  else init();
})();