🤖ChatGPT - Prompt提示选择器精简版

一个帮助用户在ChatGPT原生网页快速填写自定义提示"Prompt"的脚本。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

/**
 * 脚本逻辑概述:
 * 1. 定义常量与模板:包含样式、下拉框配置、DOM 选择器等基础参数。
 * 2. 初始化自定义提示下拉框与暗色模式样式,使 UI 贴合 ChatGPT 页面。
 * 3. 定时检查输入框容器,准备就绪后插入自定义提示下拉框并绑定事件。
 * 4. 选择自定义提示时将内容写入 ChatGPT 输入区域并自适应高度。
 * 5. 提供添加 / 删除自定义提示功能,通过 GM_setValue 与 GM_getValue 持久化存储,并同步更新下拉框选项。
 */
// ==UserScript==
// @name         🤖ChatGPT - Prompt提示选择器精简版
// @namespace    https://greasyfork.org/
// @version      1.0.9
// @description  一个帮助用户在ChatGPT原生网页快速填写自定义提示"Prompt"的脚本。
// @author       OpenAI - ChatGPT
// @require      https://ajax.aspnetcdn.com/ajax/jQuery/jquery-3.6.4.min.js
// @match        https://chat.openai.com/
// @match        https://chat.openai.com/c/*
// @match        https://chat.openai.com/?*
// @match        https://chatgpt.com/*
// @match        https://chatgpt.com/
// @match        https://chat.github-free.com/
// @match        https://chat.github-free.com/c/*
// @match        https://chat.github-free.com/?*
// @match        https://chat.zhile.io/
// @match        https://chat.zhile.io/c/*
// @match        https://chat.zhile.io/?*

// @grant        GM_setValue
// @grant        GM_getValue
// @license      GNU GPLv3

// ==/UserScript==

(function () {
    "use strict";
    const $ = window.jQuery;
    const DARK_MODE_STYLE = `
    .dark-mode-compatible {
      background-color: white;
      color: black;
    }
    .dark .dark-mode-compatible {
      background-color: #343540;
      color: white;
    }

    select {
      font-size: 16px;
      border-radius: 4px;
    }

    select option {
      font-size: 16px;
      padding: 8px;
    }

  `;

    const CUSTOM_PROMPT_DROPDOWN_ID = "customPromptDropdown";
    const CUSTOM_PROMPT_DROPDOWN_PLACEHOLDER = "[自定义提示]";
    const CUSTOM_PROMPT_DROPDOWN_WIDTH = 180;
    const DEFAULT_ENTRY_HEIGHT = 20;

    const CHECK_MARKUP_INTERVAL = 500;
    const ENTRY_HEIGHT_RESET_DELAY = 500;

    const CONTAINER_SELECTOR = "div.absolute.bottom-full"; /*在chatgpt聊天框的上面一行*/
    const ENTRY_SELECTOR = "#prompt-textarea > p";

    const OPTION_TAG_TEMPLATE =
        '<option value="{{value}}" title="{{titleMark}}">{{title}}</option>';

    const NEW_LINE = "\r\n";

    let customUIInjected = false;
    let checkTimerId = null;

    function placeholder(name) {
        return "{{" + name + "}}";
    }

    function getCustomPromptDropdown() {
        return $("#" + CUSTOM_PROMPT_DROPDOWN_ID);
    }

    function handleCustomPromptChange() {
        const customPrompt = $(this).val();
        const entry = $(ENTRY_SELECTOR);
        const button = entry.next();

        entry.text(customPrompt);

        // 计算所选自定义提示的高度,并将其应用于输入框
        const parts = customPrompt.split(NEW_LINE);
        const heightInPixels = DEFAULT_ENTRY_HEIGHT * parts.length + 1;
        entry.height(heightInPixels);

        button.prop("disabled", false);
        button.click(() => {
            setTimeout(() => {
                entry.height(DEFAULT_ENTRY_HEIGHT);
            }, ENTRY_HEIGHT_RESET_DELAY);
        });
    }

    function activateCheckTimer() {
        if (checkTimerId !== null) return;
        checkTimerId = setInterval(checkMarkup, CHECK_MARKUP_INTERVAL);
    }

    function getContainer() {
        return $(CONTAINER_SELECTOR);
    }

    /**
     * 定期检查聊天输入容器:
     * - 若已插入自定义提示 UI 或视口过窄则跳过;
     * - 否则在聊天框上方注入自定义提示下拉框并绑定交互按钮。
     */
    function checkMarkup() {
        if (customUIInjected) return;

        const container = getContainer();

        if (container.length === 0) return;
        if (window.innerWidth <= 480) return;

        const customPromptDropdown = createCustomPromptDropdown();
        customPromptDropdown.change(handleCustomPromptChange);
        addCustomPromptButton();

        customUIInjected = true;

        if (checkTimerId !== null) {
            clearInterval(checkTimerId);
            checkTimerId = null;
        }
    }

    // 添加自定义提示:收集用户输入并立即刷新下拉框
    function addCustomPrompt() {
        // 弹出输入框让用户填写标题
        const title = prompt("请输入提示的标题:");
        // 未填写标题则直接返回
        if (!title) return;
        // 读取已有自定义提示列表
        const customPrompts = JSON.parse(GM_getValue("customPrompts", "[]"));
        // 检查标题是否重复,若重复提示用户
        if (customPrompts.find((item) => item.title === title)) {
            // 标题已存在时弹窗提示
            alert("该标题已经存在,请输入一个新的标题。");
            // 阻止继续添加
            return;
        }

        // 收集用户输入的提示内容
        const content = prompt("请输入提示的内容:");
        // 未输入内容则中止
        if (!content) return;

        // 将新提示对象追加到数组中
        customPrompts.push({
            title,
            mark: content
        });
        // 持久化存储最新的自定义提示列表
        GM_setValue("customPrompts", JSON.stringify(customPrompts));

        // 获取自定义提示下拉框
        const customPromptDropdown = getCustomPromptDropdown();
        // 构造 option 字符串并插入下拉框
        customPromptDropdown.append(
            OPTION_TAG_TEMPLATE.replace(placeholder("value"), content)
            .replace(placeholder("title"), title)
            .replace(placeholder("titleMark"), content)
        );
        // 设置当前下拉框选中新添加的提示
        customPromptDropdown.val(content);
        // 触发 change 以复用既有逻辑
        customPromptDropdown.trigger("change");
        // 确保下拉框可见
        customPromptDropdown.show();
    }

    function removeCustomPrompt(mark) {
        let customPrompts = JSON.parse(GM_getValue("customPrompts", "[]"));
        customPrompts = customPrompts.filter((item) => item.mark !== mark);
        GM_setValue("customPrompts", JSON.stringify(customPrompts));
    }

    function addCustomPromptButton() {
        let addButton, removeButton;

        const container = getContainer();
        addButton = $(
            '<button class="dark-mode-compatible btn relative btn-neutral border-0 md:border" type="button">添加</button>'
        );
        addButton.click(addCustomPrompt);
        removeButton = $(
            '<button class="dark-mode-compatible btn relative btn-neutral border-0 md:border" type="button">删除</button>'
        );
        removeButton.click(() => {
            const customPromptDropdown = getCustomPromptDropdown();
            const mark = customPromptDropdown.val();
            if (!mark) return;
            customPromptDropdown.find(`option[value="${mark}"]`).remove();
            removeCustomPrompt(mark);
            if (customPromptDropdown.children().length <= 1) {
                customPromptDropdown.hide();
                removeButton.hide();
                addButton.show(); // 显示添加按钮
            }

            // 清空输入框
            const entry = $(ENTRY_SELECTOR);
            entry.text("");
            entry.height(DEFAULT_ENTRY_HEIGHT);
        });
        removeButton.hide();
        getCustomPromptDropdown().after(removeButton);
        getCustomPromptDropdown().after(addButton);

        const customPromptDropdown = getCustomPromptDropdown();
        if (customPromptDropdown.children().length <= 1) {
            removeButton.hide();
            addButton.show(); // 显示添加按钮
        }

        // 根据是否选中提示切换按钮显隐,并实时同步输入框内容
        customPromptDropdown.on("change", () => {
            if (customPromptDropdown.val()) {
                removeButton.show();
                addButton.hide(); // 隐藏添加按钮
            } else {
                removeButton.hide();
                addButton.show(); // 显示添加按钮
            }
            const selectedPrompt = $("#customPromptDropdown").val();
            if (selectedPrompt) {
                const entry = $(ENTRY_SELECTOR);
                entry.text(selectedPrompt);
            }
        });
    }




    function createCustomPromptDropdown() {
        debugger
        console.log("createCustomPromptDropdown");
        // 方便调试下拉框是否成功挂载
        const container = getContainer();
        const customPrompts = JSON.parse(GM_getValue("customPrompts", "[]"));
        const customPromptDropdown = $(
            `<select id="${CUSTOM_PROMPT_DROPDOWN_ID}" class="dark-mode-compatible" style="width: ${CUSTOM_PROMPT_DROPDOWN_WIDTH}px"></select>`
        );

        customPromptDropdown.append(
            `<option value="">${CUSTOM_PROMPT_DROPDOWN_PLACEHOLDER}</option>`
        );

        for (const customPrompt of customPrompts) {
            customPromptDropdown.append(
                `<option value="${customPrompt.mark}" title="${customPrompt.mark}">${customPrompt.title}</option>`
            );
        }

        if (customPrompts.length === 0) {
            customPromptDropdown.hide();
        } else {
            customPromptDropdown.show();
        }

        container.append(customPromptDropdown);

        return customPromptDropdown;
    }

    function addDarkModeStyles() {
        const styleTag = document.createElement("style");
        styleTag.innerHTML = DARK_MODE_STYLE;
        document.head.appendChild(styleTag);
    }

    // 初始化流程:
    // 1. 注入暗色模式样式,确保按钮与下拉框在不同主题下可读;
    // 2. 启动定时检测,一旦聊天输入容器准备好便注入自定义提示 UI。
    function init() {
        addDarkModeStyles();
        activateCheckTimer();
        checkMarkup();
    }

    // 延迟 5 秒执行初始化,确保 ChatGPT DOM 结构稳定后再挂载 UI
    setTimeout(() => {
        init();
    }, 5000);
})();