- // ==UserScript==
- // @name Prompt Manager (Fixed Vertical Drag with Copy & Close)
- // @namespace http://tampermonkey.net/
- // @version 2.7.5
- // @description 在AI网站上保存并快速使用 Prompts,同时支持拖动按钮及位置保存 —— 修复按钮只能横向拖动的问题,增大关闭按钮点击区域并上移碰撞箱,复制后显示成功并自动关闭
- // @author schweigen
- // @match https://chatgpt.com/*
- // @match https://claude.ai/*
- // @match https://chat.deepseek.com/*
- // @match https://www.perplexity.ai/*
- // @match https://chat.mistral.ai/*
- // @match https://app.nextchat.dev/*
- // @match https://chat01.ai/*
- // @match https://you.com/*
- // @match https://chatgpt.aicnm.cc/*
- // @match https://chatshare.xyz/*
- // @match https://chat.biggraph.net/*
- // @match https://grok.com/*
- // @grant GM_addStyle
- // @grant GM_setValue
- // @grant GM_getValue
- // @grant GM_registerMenuCommand
- // @grant GM_deleteValue
- // @license MIT
- // ==/UserScript==
-
- (function() {
- 'use strict';
-
- // === 用户可编辑的 Prompts 列表 ===
- const prompts = [
- {
- title: "",
- content: ``
- },
- {
- title: "",
- content: ``
- },
- {
- title: "",
- content: ``
- },
- {
- title: "",
- content: ``
- },
- {
- title: "",
- content: ``
- },
- {
- title: "",
- content: ``
- },
- {
- title: "",
- content: ``
- },
- {
- title: "",
- content: ``
- },
- {
- title: "",
- content: ``
- },
- {
- title: "",
- content: ``
- },
- ];
-
- // 添加必要的样式
- GM_addStyle(`
- /* Prompt Manager 容器样式 */
- #prompt-manager {
- position: fixed !important;
- top: 80px !important;
- right: 20px !important;
- width: 350px !important;
- max-height: 80vh !important;
- overflow-y: auto !important;
- overflow-x: visible !important;
- background: #ffffff !important;
- border: 1px solid #e1e4e8 !important;
- border-radius: 12px !important;
- box-shadow: 0 4px 16px rgba(0,0,0,0.1) !important;
- z-index: 2147483647 !important;
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif !important;
- display: block !important;
- color: #24292e !important;
- opacity: 1 !important;
- visibility: visible !important;
- }
-
- #prompt-manager.hidden {
- display: none !important;
- }
-
- /* 标题样式 */
- #prompt-manager h2 {
- margin: 0 !important;
- padding: 16px !important;
- background: #2c3e50 !important;
- color: #ffffff !important;
- border-radius: 12px 12px 0 0 !important;
- text-align: center !important;
- font-size: 18px !important;
- font-weight: 600 !important;
- position: relative !important;
- }
-
- /* 关闭按钮样式(碰撞箱上移) */
- #close-prompt-btn {
- position: absolute !important;
- top: -10px !important; /* 向上移动显示区域 */
- right: 0 !important;
- padding: 10px 16px !important;
- cursor: pointer !important;
- font-size: 20px !important;
- color: #ffffff !important;
- user-select: none !important;
- }
-
- /* Prompt 项样式 */
- .prompt-item {
- border-bottom: 1px solid #e1e4e8 !important;
- padding: 12px 16px !important;
- position: relative !important;
- transition: all 0.2s ease !important;
- background: #ffffff !important;
- }
-
- .prompt-item:hover {
- background: #f6f8fa !important;
- }
-
- .prompt-title {
- font-weight: 500 !important;
- cursor: pointer !important;
- position: relative !important;
- display: flex !important;
- justify-content: space-between !important;
- align-items: center !important;
- color: #2c3e50 !important;
- }
-
- .prompt-content {
- display: none !important;
- margin-top: 8px !important;
- white-space: pre-wrap !important;
- background: #f8f9fa !important;
- padding: 12px !important;
- border-radius: 6px !important;
- cursor: pointer !important;
- transition: background 0.2s ease !important;
- color: #2c3e50 !important;
- border: 1px solid #e1e4e8 !important;
- }
-
- .prompt-content:hover {
- background: #edf2f7 !important;
- }
-
- /* 复制按钮样式 */
- .copy-button {
- background: #3498db !important;
- color: #ffffff !important;
- border: none !important;
- padding: 6px 12px !important;
- border-radius: 4px !important;
- cursor: pointer !important;
- font-size: 12px !important;
- margin-left: 10px !important;
- transition: all 0.2s ease !important;
- }
-
- .copy-button:hover {
- background: #2980b9 !important;
- transform: translateY(-1px) !important;
- }
-
- /* Toggle 按钮样式 */
- #toggle-prompt-btn {
- position: fixed !important;
- top: 60px !important;
- right: 20px !important;
- width: 40px !important;
- height: 40px !important;
- background: #3498db !important;
- color: #ffffff !important;
- border: none !important;
- border-radius: 50% !important;
- cursor: pointer !important;
- font-size: 20px !important;
- display: flex !important;
- align-items: center !important;
- justify-content: center !important;
- z-index: 2147483647 !important;
- transition: all 0.2s ease !important;
- box-shadow: 0 2px 8px rgba(0,0,0,0.1) !important;
- opacity: 1 !important;
- visibility: visible !important;
- }
-
- #toggle-prompt-btn:hover {
- background: #2980b9 !important;
- transform: translateY(-1px) !important;
- }
-
- /* 复制成功提示样式 */
- #copy-success {
- position: fixed !important;
- top: 100px !important;
- right: 20px !important;
- background: #2ecc71 !important;
- color: #ffffff !important;
- padding: 8px 16px !important;
- border-radius: 6px !important;
- opacity: 0 !important;
- transition: opacity 0.3s ease !important;
- z-index: 2147483647 !important;
- font-size: 14px !important;
- box-shadow: 0 2px 8px rgba(0,0,0,0.1) !important;
- }
-
- /* 内部成功提示样式 */
- .inner-success {
- background: #2ecc71 !important;
- color: #ffffff !important;
- padding: 8px 12px !important;
- margin-top: 8px !important;
- border-radius: 6px !important;
- text-align: center !important;
- font-size: 14px !important;
- display: none !important;
- }
-
- /* 搜索输入框样式 */
- #search-input {
- width: calc(100% - 32px) !important;
- padding: 10px 12px !important;
- margin: 16px !important;
- border: 1px solid #e1e4e8 !important;
- border-radius: 6px !important;
- background: #f8f9fa !important;
- color: #2c3e50 !important;
- font-size: 14px !important;
- transition: all 0.2s ease !important;
- }
-
- #search-input:focus {
- outline: none !important;
- border-color: #3498db !important;
- box-shadow: 0 0 0 2px rgba(52,152,219,0.2) !important;
- }
-
- #search-input::placeholder {
- color: #95a5a6 !important;
- }
- `);
-
- // 确保DOM加载完成后再创建元素
- function createElements() {
- // 创建 Toggle 按钮
- const toggleBtn = document.createElement('button');
- toggleBtn.id = 'toggle-prompt-btn';
- toggleBtn.title = '隐藏/显示 Prompt Manager';
- toggleBtn.innerHTML = '☰';
- document.body.appendChild(toggleBtn);
-
- // 如果用户之前拖动过,则恢复按钮保存的位置
- const savedX = GM_getValue('toggleBtnX', null);
- const savedY = GM_getValue('toggleBtnY', null);
- if (savedX !== null && savedY !== null) {
- toggleBtn.style.setProperty('left', savedX + 'px', 'important');
- toggleBtn.style.setProperty('top', savedY + 'px', 'important');
- toggleBtn.style.setProperty('right', 'auto', 'important');
- }
-
- // 创建 Prompt Manager 容器,增加了关闭叉号
- const manager = document.createElement('div');
- manager.id = 'prompt-manager';
- manager.classList.add('hidden'); // 默认隐藏
- manager.innerHTML = `
- <h2>
- Prompts
- <span id="close-prompt-btn" title="关闭">×</span>
- </h2>
- <input type="text" id="search-input" placeholder="搜索 Prompts...">
- <div id="prompt-list"></div>
- `;
- document.body.appendChild(manager);
-
- // 为关闭叉号添加点击事件
- const closeBtn = document.getElementById('close-prompt-btn');
- closeBtn.addEventListener('click', () => {
- manager.classList.add('hidden');
- });
-
- // 创建复制成功提示
- const copySuccess = document.createElement('div');
- copySuccess.id = 'copy-success';
- copySuccess.textContent = '复制成功';
- document.body.appendChild(copySuccess);
-
- // 创建一个 Prompt 项
- function createPromptItem(prompt, index) {
- const item = document.createElement('div');
- item.className = 'prompt-item';
-
- const title = document.createElement('div');
- title.className = 'prompt-title';
-
- const titleText = document.createElement('span');
- titleText.textContent = prompt.title || "无标题 Prompt";
-
- const copyTitleBtn = document.createElement('button');
- copyTitleBtn.className = 'copy-button';
- copyTitleBtn.textContent = '复制';
- copyTitleBtn.title = '复制整个 Prompt 内容';
-
- // 创建内部成功提示元素
- const innerSuccess = document.createElement('div');
- innerSuccess.className = 'inner-success';
- innerSuccess.textContent = '复制成功';
- innerSuccess.style.display = 'none';
-
- copyTitleBtn.onclick = (e) => {
- e.stopPropagation();
- if (prompt.content) {
- copyToClipboard(prompt.content, item);
- } else {
- showInnerSuccess(item, '内容为空,无法复制。');
- }
- };
-
- // 仅添加标题和复制按钮
- title.appendChild(titleText);
- title.appendChild(copyTitleBtn);
-
- const content = document.createElement('div');
- content.className = 'prompt-content';
- content.textContent = prompt.content || "无内容 Prompt";
-
- content.addEventListener('click', () => {
- if (prompt.content) {
- copyToClipboard(prompt.content, item);
- } else {
- showInnerSuccess(item, '内容为空,无法复制。');
- }
- });
-
- // 仅添加点击切换内容显示
- title.addEventListener('click', () => {
- const isVisible = content.style.display === 'block';
- content.style.display = isVisible ? 'none' : 'block';
- });
-
- item.appendChild(title);
- item.appendChild(content);
- item.appendChild(innerSuccess); // 添加内部成功提示
-
- return item;
- }
-
- // 渲染 Prompts 列表
- function renderPrompts(filter = '') {
- const promptList = document.getElementById('prompt-list');
- promptList.innerHTML = '';
- const filtered = prompts.filter(p =>
- (p.title && p.title.toLowerCase().includes(filter.toLowerCase())) ||
- (p.content && p.content.toLowerCase().includes(filter.toLowerCase()))
- );
- filtered.forEach((prompt, index) => {
- const item = createPromptItem(prompt, index);
- promptList.appendChild(item);
- });
- }
-
- // 复制到剪贴板并显示成功提示
- function copyToClipboard(text, promptItem) {
- navigator.clipboard.writeText(text).then(() => {
- showInnerSuccess(promptItem, '复制成功');
- }).catch(err => {
- console.error('复制失败: ', err);
- showInnerSuccess(promptItem, '复制失败,请手动复制。');
- });
- }
-
- // 显示成功提示并立即关闭面板
- function showInnerSuccess(promptItem, message = '复制成功') {
- // 直接关闭面板,不显示内部提示
- document.getElementById('prompt-manager').classList.add('hidden');
-
- // 在外部显示一个简短的提示
- showCopySuccess(message);
- }
-
- // 显示复制成功提示(保留旧函数以兼容)
- function showCopySuccess(message = '复制成功') {
- copySuccess.textContent = message;
- copySuccess.style.opacity = '1';
- setTimeout(() => {
- copySuccess.style.opacity = '0';
- }, 1500);
- }
-
- // ======= 以下为拖拽功能 =======
- let isDragging = false, justDragged = false, startX, startY, origLeft, origTop;
-
- toggleBtn.addEventListener('mousedown', function(e) {
- if (e.button !== 0) return; // 仅响应鼠标左键
- isDragging = false;
- startX = e.clientX;
- startY = e.clientY;
- // 获取当前按钮的位置(相对于视口)
- const rect = toggleBtn.getBoundingClientRect();
- origLeft = rect.left;
- origTop = rect.top;
-
- function onMouseMove(e) {
- const dx = e.clientX - startX;
- const dy = e.clientY - startY;
- if (!isDragging) {
- // 超过 5px 视为拖拽操作
- if (Math.abs(dx) > 5 || Math.abs(dy) > 5) {
- isDragging = true;
- }
- }
- if (isDragging) {
- // 使用 setProperty 带上 'important' 以覆盖样式中的 !important
- toggleBtn.style.setProperty('left', (origLeft + dx) + 'px', 'important');
- toggleBtn.style.setProperty('top', (origTop + dy) + 'px', 'important');
- toggleBtn.style.setProperty('right', 'auto', 'important');
- e.preventDefault();
- }
- }
-
- function onMouseUp(e) {
- document.removeEventListener('mousemove', onMouseMove);
- document.removeEventListener('mouseup', onMouseUp);
- if (isDragging) {
- justDragged = true;
- // 保存新位置
- const newLeft = parseInt(toggleBtn.style.left, 10);
- const newTop = parseInt(toggleBtn.style.top, 10);
- GM_setValue('toggleBtnX', newLeft);
- GM_setValue('toggleBtnY', newTop);
- }
- }
-
- document.addEventListener('mousemove', onMouseMove);
- document.addEventListener('mouseup', onMouseUp);
- });
-
- // 修改点击事件,避免拖拽后触发点击
- toggleBtn.addEventListener('click', (e) => {
- if (justDragged) {
- justDragged = false;
- return;
- }
- manager.classList.toggle('hidden');
- });
-
- // Toggle 按钮快捷键显示/隐藏 Prompt Manager (Ctrl/Command + O)
- document.addEventListener('keydown', (e) => {
- const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0;
- const modifier = isMac ? e.metaKey : e.ctrlKey;
-
- if (modifier && e.key.toLowerCase() === 'o') {
- e.preventDefault();
- manager.classList.toggle('hidden');
- }
- });
-
- // 搜索 Prompts
- const searchInput = document.getElementById('search-input');
- searchInput.addEventListener('input', () => {
- renderPrompts(searchInput.value);
- });
-
- // 初始渲染
- renderPrompts();
- }
-
- // 确保DOM加载完成后再创建元素
- if (document.readyState === 'loading') {
- document.addEventListener('DOMContentLoaded', createElements);
- } else {
- createElements();
- }
-
- // 每隔一秒检查一次是否需要重新创建元素(用于处理某些网站的动态加载)
- let checkInterval = setInterval(() => {
- if (!document.getElementById('toggle-prompt-btn')) {
- createElements();
- }
- }, 1000);
-
- // 5分钟后停止检查,以避免无限循环
- setTimeout(() => {
- clearInterval(checkInterval);
- }, 300000); // 5分钟
-
- // ======= 添加油猴菜单命令,用于重置按钮默认位置 =======
- GM_registerMenuCommand("重置按钮默认位置", () => {
- GM_deleteValue('toggleBtnX');
- GM_deleteValue('toggleBtnY');
- const toggleBtn = document.getElementById('toggle-prompt-btn');
- if (toggleBtn) {
- toggleBtn.style.setProperty('top', '60px', 'important');
- toggleBtn.style.setProperty('right', '20px', 'important');
- toggleBtn.style.removeProperty('left');
- }
- });
- })();