Prompt Manager (Fixed Vertical Drag with Copy & Close)

在AI网站上保存并快速使用 Prompts,同时支持拖动按钮及位置保存 —— 修复按钮只能横向拖动的问题,增大关闭按钮点击区域并上移碰撞箱,复制后显示成功并自动关闭

  1. // ==UserScript==
  2. // @name Prompt Manager (Fixed Vertical Drag with Copy & Close)
  3. // @namespace http://tampermonkey.net/
  4. // @version 2.7.6
  5. // @description 在AI网站上保存并快速使用 Prompts,同时支持拖动按钮及位置保存 —— 修复按钮只能横向拖动的问题,增大关闭按钮点击区域并上移碰撞箱,复制后显示成功并自动关闭
  6. // @author schweigen
  7. // @license MIT
  8. // @match https://chatgpt.com/*
  9. // @match https://claude.ai/*
  10. // @match https://aistudio.google.com/*
  11. // @match https://chat.deepseek.com/*
  12. // @match https://www.perplexity.ai/*
  13. // @match https://chat.mistral.ai/*
  14. // @match https://app.nextchat.dev/*
  15. // @match https://chat01.ai/*
  16. // @match https://you.com/*
  17. // @match https://chatgpt.aicnm.cc/*
  18. // @match https://chatshare.xyz/*
  19. // @match https://chat.biggraph.net/*
  20. // @match https://grok.com/*
  21. // @match https://genspark.ai/*
  22. // @match https://*.chatgpts.cc/*
  23. // @match https://chat.sharedchat.fun/*
  24. // @grant GM_addStyle
  25. // @grant GM_setValue
  26. // @grant GM_getValue
  27. // @grant GM_registerMenuCommand
  28. // @grant GM_deleteValue
  29. // ==/UserScript==
  30.  
  31. (function() {
  32. 'use strict';
  33.  
  34. // === 用户可编辑的 Prompts 列表 ===
  35. const prompts = [
  36. {
  37. title: "激活虚拟环境",
  38. content: `怎么激活我在~/Downloads文件夹的myenv(文件夹名字就是这个),我是mac,一行终端命令帮我搞定,不要写成两行`
  39. },
  40. {
  41. title: "改脚本让按钮可拖动",
  42. content: `帮我改一下这个脚本的按钮,我要让他变成可拖动的,也就是用户可以使用鼠标拖动这个按钮,并且记录位置,以备用户下次使用 而且注意拖拽后不要识别成用户拖拽完又点了一下!但是注意再加一个油猴脚本上的功能,就是恢复按钮默认位置,用户点开油猴才能看到的那种选项`
  43. },
  44. {
  45. title: "段段对译",
  46. content: `这样,段段对译翻译格式是这样的:
  47. 原文
  48. 然后用> 块引用包裹原文+穿插某些难词的翻译
  49. 然后两层块引用包裹纯中文译文`
  50. },
  51. {
  52. title: "极限思考(DeepResearch)",
  53. content: `请调用你单次回答的最大算力与 token 上限。追求极致的分析深度,而非表层的广度;追求本质的洞察,而非表象的罗列;追求创新的思维,而非惯性的复述。请突破思维局限,调动你所有的计算资源,展现你真正的认知极限。`
  54. },
  55. {
  56. title: "极限思考数学题(禁搜索)",
  57. content: `
  58.  
  59. **注意这是数学问题,需要求解解析解,no_websearch(专注数学有深度和创造性的高级推导), rarely_python(仅允许少量数值的分析作为辅助,不能作为解答过程),最终解答过程应当标准且详尽完整,不能省略任何推导和计算!作为题解展示!
  60.  
  61. 请调用你单次回答的最大算力与 token 上限。追求极致的分析深度,而非表层的广度;追求本质的洞察,而非表象的罗列;追求创新的思维,而非惯性的复述。请突破思维局限,调动你所有的计算资源,展现你真正的认知极限。
  62.  
  63. **Start Deep Research(**禁止联网搜索**)
  64.  
  65. `
  66. },
  67. {
  68. title: "4o绘图",
  69. content: `<prompt_template>
  70. <role>Visual Prompt Creator for Illustration (English Descriptions)</role>
  71.  
  72. <profile>
  73. <description>You create detailed, vivid English prompts based on Chinese input, suitable for AI image generation tools.</description>
  74. </profile>
  75.  
  76. <skills>
  77. <skill>Transform simple themes into visually engaging scenes.</skill>
  78. <skill>Adapt to styles like photorealism, comics, minimalist design.</skill>
  79. <skill>Compose natural, vivid English prompts with emotional tone.</skill>
  80. <skill>Understand layout, lighting, materials, and composition.</skill>
  81. </skills>
  82.  
  83. <rules>
  84. <rule>Input must be in Chinese; output prompt must be in English.</rule>
  85. <rule>Communicate with the user in Chinese only.</rule>
  86. <rule>Describe scenes from top to bottom, left to right.</rule>
  87. <rule>Indicate image shape (e.g., square, wide, vertical).</rule>
  88. <rule>Be specific; avoid vague descriptions.</rule>
  89. </rules>
  90.  
  91. <output_format>
  92. <step>Start with one sentence: visual style + image shape + intent.</step>
  93. <step>Describe layout and content from top to bottom, left to right.</step>
  94. <step>Include subjects, materials, colors, lighting, and text if present.</step>
  95. <step>Ensure language is cinematic and ready for generation.</step>
  96. </output_format>
  97.  
  98. <workflow>
  99. <step>Receive Chinese theme and (optionally) style or shape.</step>
  100. <step>Analyze visual potential and emotional tone.</step>
  101. <step>Select style and appropriate image shape.</step>
  102. <step>Create an English prompt with full visual details.</step>
  103. <step>Output prompt for image generation tools.</step>
  104. </workflow>
  105.  
  106. <initialization>你好!请用中文描述你想创作的画面主题(可指定风格或图像比例),我将为你生成英文绘画提示词 🎨</initialization>
  107.  
  108. <examples>
  109. <example id="1">
  110. <title>Top-selling cocktails (photorealistic)</title>
  111. <prompt><![CDATA[
  112. A square image, with a clean, photorealistic overhead composition showcasing the four top-selling cocktails in my bar.
  113.  
  114. At the top:
  115. - Title in serif font: "4 Most Popular Cocktails"
  116.  
  117. From top to bottom, left to right:
  118. - Four cocktails on a white glossy surface.
  119. - In front of each, a brown kraft recipe card with black cursive text.
  120. - Each card shows drink name and ingredients.
  121.  
  122. Background:
  123. - White, soft overhead lighting, minimal shadows.
  124. ]]></prompt>
  125. </example>
  126.  
  127. <example id="2">
  128. <title>Snail buys a sports car (comic strip)</title>
  129. <prompt><![CDATA[
  130. A wide image, formatted as a 4-panel comic strip.
  131.  
  132. Panel 1 (top-left):
  133. - A small snail at a flashy showroom counter.
  134. - Salesman leaning way over to see it.
  135.  
  136. Panel 2 (top-right):
  137. - Close-up of the snail saying: "I want your fastest sports car... and big S’s on every side."
  138.  
  139. Panel 3 (bottom-left):
  140. - Salesman scratching his head: "Why the S’s?"
  141.  
  142. Panel 4 (bottom-right):
  143. - Red sports car zooms by, covered in huge Ss.
  144. - Crowd: "WOW! LOOK AT THAT S-CAR GO!"
  145. ]]></prompt>
  146. </example>
  147. </examples>
  148. </prompt_template>
  149.  
  150. `
  151. },
  152. {
  153. title: "",
  154. content: ``
  155. },
  156. {
  157. title: "",
  158. content: ``
  159. },
  160. {
  161. title: "",
  162. content: ``
  163. },
  164. {
  165. title: "",
  166. content: ``
  167. },
  168. ];
  169.  
  170. // 添加必要的样式
  171. GM_addStyle(`
  172. /* Prompt Manager 容器样式 */
  173. #prompt-manager {
  174. position: fixed !important;
  175. top: 80px !important;
  176. right: 20px !important;
  177. width: 350px !important;
  178. max-height: 80vh !important;
  179. overflow-y: auto !important;
  180. overflow-x: visible !important;
  181. background: #ffffff !important;
  182. border: 1px solid #e1e4e8 !important;
  183. border-radius: 12px !important;
  184. box-shadow: 0 4px 16px rgba(0,0,0,0.1) !important;
  185. z-index: 2147483647 !important;
  186. font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif !important;
  187. display: block !important;
  188. color: #24292e !important;
  189. opacity: 1 !important;
  190. visibility: visible !important;
  191. }
  192.  
  193. #prompt-manager.hidden {
  194. display: none !important;
  195. }
  196.  
  197. /* 标题样式 */
  198. #prompt-manager h2 {
  199. margin: 0 !important;
  200. padding: 16px !important;
  201. background: #2c3e50 !important;
  202. color: #ffffff !important;
  203. border-radius: 12px 12px 0 0 !important;
  204. text-align: center !important;
  205. font-size: 18px !important;
  206. font-weight: 600 !important;
  207. position: relative !important;
  208. }
  209.  
  210. /* 关闭按钮样式(碰撞箱上移) */
  211. #close-prompt-btn {
  212. position: absolute !important;
  213. top: -10px !important; /* 向上移动显示区域 */
  214. right: 0 !important;
  215. padding: 10px 16px !important;
  216. cursor: pointer !important;
  217. font-size: 20px !important;
  218. color: #ffffff !important;
  219. user-select: none !important;
  220. }
  221.  
  222. /* Prompt 项样式 */
  223. .prompt-item {
  224. border-bottom: 1px solid #e1e4e8 !important;
  225. padding: 12px 16px !important;
  226. position: relative !important;
  227. transition: all 0.2s ease !important;
  228. background: #ffffff !important;
  229. }
  230.  
  231. .prompt-item:hover {
  232. background: #f6f8fa !important;
  233. }
  234.  
  235. .prompt-title {
  236. font-weight: 500 !important;
  237. cursor: pointer !important;
  238. position: relative !important;
  239. display: flex !important;
  240. justify-content: space-between !important;
  241. align-items: center !important;
  242. color: #2c3e50 !important;
  243. }
  244.  
  245. .prompt-content {
  246. display: none !important;
  247. margin-top: 8px !important;
  248. white-space: pre-wrap !important;
  249. background: #f8f9fa !important;
  250. padding: 12px !important;
  251. border-radius: 6px !important;
  252. cursor: pointer !important;
  253. transition: background 0.2s ease !important;
  254. color: #2c3e50 !important;
  255. border: 1px solid #e1e4e8 !important;
  256. }
  257.  
  258. .prompt-content:hover {
  259. background: #edf2f7 !important;
  260. }
  261.  
  262. /* 复制按钮样式 */
  263. .copy-button {
  264. background: #3498db !important;
  265. color: #ffffff !important;
  266. border: none !important;
  267. padding: 6px 12px !important;
  268. border-radius: 4px !important;
  269. cursor: pointer !important;
  270. font-size: 12px !important;
  271. margin-left: 10px !important;
  272. transition: all 0.2s ease !important;
  273. }
  274.  
  275. .copy-button:hover {
  276. background: #2980b9 !important;
  277. transform: translateY(-1px) !important;
  278. }
  279.  
  280. /* Toggle 按钮样式 */
  281. #toggle-prompt-btn {
  282. position: fixed !important;
  283. top: 60px !important;
  284. right: 20px !important;
  285. width: 40px !important;
  286. height: 40px !important;
  287. background: #3498db !important;
  288. color: #ffffff !important;
  289. border: none !important;
  290. border-radius: 50% !important;
  291. cursor: pointer !important;
  292. font-size: 20px !important;
  293. display: flex !important;
  294. align-items: center !important;
  295. justify-content: center !important;
  296. z-index: 2147483647 !important;
  297. transition: all 0.2s ease !important;
  298. box-shadow: 0 2px 8px rgba(0,0,0,0.1) !important;
  299. opacity: 1 !important;
  300. visibility: visible !important;
  301. }
  302.  
  303. #toggle-prompt-btn:hover {
  304. background: #2980b9 !important;
  305. transform: translateY(-1px) !important;
  306. }
  307.  
  308. /* 复制成功提示样式 */
  309. #copy-success {
  310. position: fixed !important;
  311. top: 100px !important;
  312. right: 20px !important;
  313. background: #2ecc71 !important;
  314. color: #ffffff !important;
  315. padding: 8px 16px !important;
  316. border-radius: 6px !important;
  317. opacity: 0 !important;
  318. transition: opacity 0.3s ease !important;
  319. z-index: 2147483647 !important;
  320. font-size: 14px !important;
  321. box-shadow: 0 2px 8px rgba(0,0,0,0.1) !important;
  322. }
  323.  
  324. /* 内部成功提示样式 */
  325. .inner-success {
  326. background: #2ecc71 !important;
  327. color: #ffffff !important;
  328. padding: 8px 12px !important;
  329. margin-top: 8px !important;
  330. border-radius: 6px !important;
  331. text-align: center !important;
  332. font-size: 14px !important;
  333. display: none !important;
  334. }
  335.  
  336. /* 搜索输入框样式 */
  337. #search-input {
  338. width: calc(100% - 32px) !important;
  339. padding: 10px 12px !important;
  340. margin: 16px !important;
  341. border: 1px solid #e1e4e8 !important;
  342. border-radius: 6px !important;
  343. background: #f8f9fa !important;
  344. color: #2c3e50 !important;
  345. font-size: 14px !important;
  346. transition: all 0.2s ease !important;
  347. }
  348.  
  349. #search-input:focus {
  350. outline: none !important;
  351. border-color: #3498db !important;
  352. box-shadow: 0 0 0 2px rgba(52,152,219,0.2) !important;
  353. }
  354.  
  355. #search-input::placeholder {
  356. color: #95a5a6 !important;
  357. }
  358. `);
  359.  
  360. // 确保DOM加载完成后再创建元素
  361. function createElements() {
  362. // 创建 Toggle 按钮
  363. const toggleBtn = document.createElement('button');
  364. toggleBtn.id = 'toggle-prompt-btn';
  365. toggleBtn.title = '隐藏/显示 Prompt Manager';
  366. toggleBtn.innerHTML = '&#9776;';
  367. document.body.appendChild(toggleBtn);
  368.  
  369. // 如果用户之前拖动过,则恢复按钮保存的位置
  370. const savedX = GM_getValue('toggleBtnX', null);
  371. const savedY = GM_getValue('toggleBtnY', null);
  372. if (savedX !== null && savedY !== null) {
  373. toggleBtn.style.setProperty('left', savedX + 'px', 'important');
  374. toggleBtn.style.setProperty('top', savedY + 'px', 'important');
  375. toggleBtn.style.setProperty('right', 'auto', 'important');
  376. }
  377.  
  378. // 创建 Prompt Manager 容器,增加了关闭叉号
  379. const manager = document.createElement('div');
  380. manager.id = 'prompt-manager';
  381. manager.classList.add('hidden'); // 默认隐藏
  382. manager.innerHTML = `
  383. <h2>
  384. Prompts
  385. <span id="close-prompt-btn" title="关闭">×</span>
  386. </h2>
  387. <input type="text" id="search-input" placeholder="搜索 Prompts...">
  388. <div id="prompt-list"></div>
  389. `;
  390. document.body.appendChild(manager);
  391.  
  392. // 为关闭叉号添加点击事件
  393. const closeBtn = document.getElementById('close-prompt-btn');
  394. closeBtn.addEventListener('click', () => {
  395. manager.classList.add('hidden');
  396. });
  397.  
  398. // 创建复制成功提示
  399. const copySuccess = document.createElement('div');
  400. copySuccess.id = 'copy-success';
  401. copySuccess.textContent = '复制成功';
  402. document.body.appendChild(copySuccess);
  403.  
  404. // 创建一个 Prompt 项
  405. function createPromptItem(prompt, index) {
  406. const item = document.createElement('div');
  407. item.className = 'prompt-item';
  408.  
  409. const title = document.createElement('div');
  410. title.className = 'prompt-title';
  411.  
  412. const titleText = document.createElement('span');
  413. titleText.textContent = prompt.title || "无标题 Prompt";
  414.  
  415. const copyTitleBtn = document.createElement('button');
  416. copyTitleBtn.className = 'copy-button';
  417. copyTitleBtn.textContent = '复制';
  418. copyTitleBtn.title = '复制整个 Prompt 内容';
  419.  
  420. // 创建内部成功提示元素
  421. const innerSuccess = document.createElement('div');
  422. innerSuccess.className = 'inner-success';
  423. innerSuccess.textContent = '复制成功';
  424. innerSuccess.style.display = 'none';
  425.  
  426. copyTitleBtn.onclick = (e) => {
  427. e.stopPropagation();
  428. if (prompt.content) {
  429. copyToClipboard(prompt.content, item);
  430. } else {
  431. showInnerSuccess(item, '内容为空,无法复制。');
  432. }
  433. };
  434.  
  435. // 仅添加标题和复制按钮
  436. title.appendChild(titleText);
  437. title.appendChild(copyTitleBtn);
  438.  
  439. const content = document.createElement('div');
  440. content.className = 'prompt-content';
  441. content.textContent = prompt.content || "无内容 Prompt";
  442.  
  443. content.addEventListener('click', () => {
  444. if (prompt.content) {
  445. copyToClipboard(prompt.content, item);
  446. } else {
  447. showInnerSuccess(item, '内容为空,无法复制。');
  448. }
  449. });
  450.  
  451. // 仅添加点击切换内容显示
  452. title.addEventListener('click', () => {
  453. const isVisible = content.style.display === 'block';
  454. content.style.display = isVisible ? 'none' : 'block';
  455. });
  456.  
  457. item.appendChild(title);
  458. item.appendChild(content);
  459. item.appendChild(innerSuccess); // 添加内部成功提示
  460.  
  461. return item;
  462. }
  463.  
  464. // 渲染 Prompts 列表
  465. function renderPrompts(filter = '') {
  466. const promptList = document.getElementById('prompt-list');
  467. promptList.innerHTML = '';
  468. const filtered = prompts.filter(p =>
  469. (p.title && p.title.toLowerCase().includes(filter.toLowerCase())) ||
  470. (p.content && p.content.toLowerCase().includes(filter.toLowerCase()))
  471. );
  472. filtered.forEach((prompt, index) => {
  473. const item = createPromptItem(prompt, index);
  474. promptList.appendChild(item);
  475. });
  476. }
  477.  
  478. // 复制到剪贴板并显示成功提示
  479. function copyToClipboard(text, promptItem) {
  480. navigator.clipboard.writeText(text).then(() => {
  481. showInnerSuccess(promptItem, '复制成功');
  482. }).catch(err => {
  483. console.error('复制失败: ', err);
  484. showInnerSuccess(promptItem, '复制失败,请手动复制。');
  485. });
  486. }
  487.  
  488. // 显示成功提示并立即关闭面板
  489. function showInnerSuccess(promptItem, message = '复制成功') {
  490. // 直接关闭面板,不显示内部提示
  491. document.getElementById('prompt-manager').classList.add('hidden');
  492.  
  493. // 在外部显示一个简短的提示
  494. showCopySuccess(message);
  495. }
  496.  
  497. // 显示复制成功提示(保留旧函数以兼容)
  498. function showCopySuccess(message = '复制成功') {
  499. copySuccess.textContent = message;
  500. copySuccess.style.opacity = '1';
  501. setTimeout(() => {
  502. copySuccess.style.opacity = '0';
  503. }, 1500);
  504. }
  505.  
  506. // ======= 以下为拖拽功能 =======
  507. let isDragging = false, justDragged = false, startX, startY, origLeft, origTop;
  508.  
  509. toggleBtn.addEventListener('mousedown', function(e) {
  510. if (e.button !== 0) return; // 仅响应鼠标左键
  511. isDragging = false;
  512. startX = e.clientX;
  513. startY = e.clientY;
  514. // 获取当前按钮的位置(相对于视口)
  515. const rect = toggleBtn.getBoundingClientRect();
  516. origLeft = rect.left;
  517. origTop = rect.top;
  518.  
  519. function onMouseMove(e) {
  520. const dx = e.clientX - startX;
  521. const dy = e.clientY - startY;
  522. if (!isDragging) {
  523. // 超过 5px 视为拖拽操作
  524. if (Math.abs(dx) > 5 || Math.abs(dy) > 5) {
  525. isDragging = true;
  526. }
  527. }
  528. if (isDragging) {
  529. // 使用 setProperty 带上 'important' 以覆盖样式中的 !important
  530. toggleBtn.style.setProperty('left', (origLeft + dx) + 'px', 'important');
  531. toggleBtn.style.setProperty('top', (origTop + dy) + 'px', 'important');
  532. toggleBtn.style.setProperty('right', 'auto', 'important');
  533. e.preventDefault();
  534. }
  535. }
  536.  
  537. function onMouseUp(e) {
  538. document.removeEventListener('mousemove', onMouseMove);
  539. document.removeEventListener('mouseup', onMouseUp);
  540. if (isDragging) {
  541. justDragged = true;
  542. // 保存新位置
  543. const newLeft = parseInt(toggleBtn.style.left, 10);
  544. const newTop = parseInt(toggleBtn.style.top, 10);
  545. GM_setValue('toggleBtnX', newLeft);
  546. GM_setValue('toggleBtnY', newTop);
  547. }
  548. }
  549.  
  550. document.addEventListener('mousemove', onMouseMove);
  551. document.addEventListener('mouseup', onMouseUp);
  552. });
  553.  
  554. // 修改点击事件,避免拖拽后触发点击
  555. toggleBtn.addEventListener('click', (e) => {
  556. if (justDragged) {
  557. justDragged = false;
  558. return;
  559. }
  560. manager.classList.toggle('hidden');
  561. });
  562.  
  563. // Toggle 按钮快捷键显示/隐藏 Prompt Manager (Ctrl/Command + O)
  564. document.addEventListener('keydown', (e) => {
  565. const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0;
  566. const modifier = isMac ? e.metaKey : e.ctrlKey;
  567.  
  568. if (modifier && e.key.toLowerCase() === 'o') {
  569. e.preventDefault();
  570. manager.classList.toggle('hidden');
  571. }
  572. });
  573.  
  574. // 搜索 Prompts
  575. const searchInput = document.getElementById('search-input');
  576. searchInput.addEventListener('input', () => {
  577. renderPrompts(searchInput.value);
  578. });
  579.  
  580. // 初始渲染
  581. renderPrompts();
  582. }
  583.  
  584. // 确保DOM加载完成后再创建元素
  585. if (document.readyState === 'loading') {
  586. document.addEventListener('DOMContentLoaded', createElements);
  587. } else {
  588. createElements();
  589. }
  590.  
  591. // 每隔一秒检查一次是否需要重新创建元素(用于处理某些网站的动态加载)
  592. let checkInterval = setInterval(() => {
  593. if (!document.getElementById('toggle-prompt-btn')) {
  594. createElements();
  595. }
  596. }, 1000);
  597.  
  598. // 5分钟后停止检查,以避免无限循环
  599. setTimeout(() => {
  600. clearInterval(checkInterval);
  601. }, 300000); // 5分钟
  602.  
  603. // ======= 添加油猴菜单命令,用于重置按钮默认位置 =======
  604. GM_registerMenuCommand("重置按钮默认位置", () => {
  605. GM_deleteValue('toggleBtnX');
  606. GM_deleteValue('toggleBtnY');
  607. const toggleBtn = document.getElementById('toggle-prompt-btn');
  608. if (toggleBtn) {
  609. toggleBtn.style.setProperty('top', '60px', 'important');
  610. toggleBtn.style.setProperty('right', '20px', 'important');
  611. toggleBtn.style.removeProperty('left');
  612. }
  613. });
  614. })();