Promptimizer

AI-powered prompt optimization tool that works with OpenAI-compatible APIs

目前為 2025-02-28 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name Promptimizer
  3. // @namespace https://github.com/NoahTheGinger/Promptimizer/
  4. // @version 1.2
  5. // @description AI-powered prompt optimization tool that works with OpenAI-compatible APIs
  6. // @author NoahTheGinger
  7. // @namespace https://github.com/NoahTheGinger/Promptimizer/
  8. // @match *://*/*
  9. // @license MIT
  10. // @grant GM_xmlhttpRequest
  11. // @grant GM_setValue
  12. // @grant GM_getValue
  13. // @grant GM_addStyle
  14. // ==/UserScript==
  15.  
  16. (function() {
  17. 'use strict';
  18.  
  19. // Styles for the UI
  20. GM_addStyle(`
  21. #promptimizer-container {
  22. position: fixed;
  23. /*
  24. UI CHANGE:
  25. Default position now near bottom-right.
  26. This corresponds with the toggle's new position in the bottom-right corner.
  27. */
  28. bottom: 80px;
  29. right: 20px;
  30. width: 400px;
  31. height: auto; /* Let container height auto-adjust initially */
  32. background: #ffffff;
  33. border-radius: 8px;
  34. box-shadow: 0 2px 10px rgba(0,0,0,0.3);
  35. z-index: 2147483647;
  36. font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
  37. display: none;
  38. color: #000000;
  39. /* UI CHANGE: Added transform and opacity transitions for smoother show/hide + repositioning. */
  40. transition:
  41. opacity 0.2s ease,
  42. width 0.2s ease,
  43. height 0.2s ease,
  44. top 0.2s ease,
  45. left 0.2s ease,
  46. right 0.2s ease,
  47. bottom 0.2s ease;
  48. overflow: hidden;
  49. min-height: 300px;
  50. min-width: 300px;
  51. opacity: 0; /* For fade effect upon showing */
  52. }
  53. /* UI CHANGE: A "show" class that toggles visibility with a fade */
  54. #promptimizer-container.show {
  55. opacity: 1;
  56. }
  57.  
  58. #promptimizer-header {
  59. padding: 12px;
  60. background: #2196F3;
  61. border-radius: 8px 8px 0 0;
  62. cursor: move;
  63. display: flex;
  64. justify-content: space-between;
  65. align-items: center;
  66. color: #ffffff;
  67. user-select: none; /* UI CHANGE: Prevent text selection while dragging header */
  68. }
  69.  
  70. #promptimizer-header-controls {
  71. display: flex;
  72. gap: 12px;
  73. align-items: center;
  74. }
  75.  
  76. .header-button {
  77. background: none;
  78. border: none;
  79. color: #ffffff;
  80. cursor: pointer;
  81. font-size: 16px;
  82. padding: 0;
  83. display: flex;
  84. align-items: center;
  85. opacity: 0.8;
  86. transition: opacity 0.2s;
  87. }
  88.  
  89. .header-button:hover {
  90. opacity: 1;
  91. }
  92.  
  93. #promptimizer-title {
  94. margin: 0;
  95. font-size: 16px;
  96. font-weight: bold;
  97. color: #ffffff;
  98. }
  99.  
  100. #promptimizer-toggle {
  101. position: fixed;
  102. /*
  103. UI CHANGE:
  104. Toggle button now in bottom-right corner.
  105. */
  106. bottom: 20px;
  107. right: 20px;
  108. width: 50px;
  109. height: 50px;
  110. background: #2196F3;
  111. border-radius: 25px;
  112. color: white;
  113. display: flex;
  114. align-items: center;
  115. justify-content: center;
  116. cursor: pointer;
  117. font-size: 24px;
  118. box-shadow: 0 2px 5px rgba(0,0,0,0.2);
  119. z-index: 2147483647;
  120. border: 2px solid rgba(255,255,255,0.2);
  121. }
  122.  
  123. #promptimizer-content {
  124. display: flex;
  125. flex-direction: column;
  126. background: #ffffff;
  127. overflow: hidden;
  128. max-height: calc(100% - 48px);
  129. }
  130.  
  131. .scrollable-flex {
  132. overflow-y: auto;
  133. flex: 1;
  134. padding: 16px;
  135. }
  136.  
  137. .promptimizer-input {
  138. width: 100%;
  139. padding: 8px;
  140. margin-bottom: 10px;
  141. border: 1px solid #ddd;
  142. border-radius: 4px;
  143. box-sizing: border-box;
  144. background: #ffffff;
  145. color: #000000;
  146. }
  147.  
  148. textarea.promptimizer-input {
  149. resize: none;
  150. min-height: 100px;
  151. transition: border-color 0.2s ease, box-shadow 0.2s ease;
  152. overflow-y: auto;
  153. }
  154.  
  155. textarea.promptimizer-input:focus {
  156. border-color: #2196F3;
  157. box-shadow: 0 0 0 2px rgba(33, 150, 243, 0.2);
  158. outline: none;
  159. }
  160.  
  161. .prompt-type-select {
  162. width: 100%;
  163. padding: 8px;
  164. margin-bottom: 10px;
  165. border: 1px solid #ddd;
  166. border-radius: 4px;
  167. background: #ffffff;
  168. color: #000000;
  169. font-size: 14px;
  170. cursor: pointer;
  171. }
  172.  
  173. .prompt-type-select:focus {
  174. border-color: #2196F3;
  175. outline: none;
  176. }
  177.  
  178. .input-group {
  179. margin-bottom: 16px;
  180. }
  181.  
  182. .input-label {
  183. display: block;
  184. margin-bottom: 6px;
  185. color: #000000;
  186. font-size: 14px;
  187. font-weight: 500;
  188. }
  189.  
  190. .promptimizer-button {
  191. background: #2196F3;
  192. color: white;
  193. border: none;
  194. padding: 8px 16px;
  195. border-radius: 4px;
  196. cursor: pointer;
  197. font-size: 14px;
  198. margin-bottom: 10px;
  199. width: 100%;
  200. }
  201.  
  202. .promptimizer-button:hover {
  203. background: #1976D2;
  204. }
  205.  
  206. #promptimizer-response-container {
  207. position: relative;
  208. margin-top: 16px;
  209. }
  210.  
  211. #promptimizer-response {
  212. margin-top: 0;
  213. padding: 12px;
  214. background: #f8f9fa;
  215. border-radius: 4px;
  216. white-space: pre-wrap;
  217. max-height: 300px;
  218. overflow-y: auto;
  219. color: #000000;
  220. border: 1px solid #e9ecef;
  221. }
  222.  
  223. #copy-button {
  224. position: absolute;
  225. top: 8px;
  226. right: 8px;
  227. background: #2196F3;
  228. color: white;
  229. border: none;
  230. border-radius: 4px;
  231. padding: 4px 8px;
  232. font-size: 12px;
  233. cursor: pointer;
  234. opacity: 0;
  235. transition: opacity 0.2s;
  236. z-index: 1;
  237. }
  238.  
  239. #copy-button:hover {
  240. background: #1976D2;
  241. }
  242.  
  243. #copy-button.visible {
  244. opacity: 1;
  245. }
  246.  
  247. #copy-button.copied {
  248. background: #4CAF50;
  249. }
  250.  
  251. .promptimizer-error {
  252. color: #dc3545;
  253. margin-top: 8px;
  254. font-size: 14px;
  255. background: #fff;
  256. padding: 8px;
  257. border-radius: 4px;
  258. }
  259.  
  260. .config-section {
  261. margin-bottom: 15px;
  262. overflow: hidden;
  263. transition: max-height 0.3s ease-out;
  264. }
  265.  
  266. .config-section.collapsed {
  267. max-height: 40px;
  268. }
  269.  
  270. .config-section.expanded {
  271. max-height: 300px;
  272. }
  273.  
  274. .config-header {
  275. display: flex;
  276. justify-content: space-between;
  277. align-items: center;
  278. cursor: pointer;
  279. padding: 8px 0;
  280. }
  281.  
  282. .config-header h3 {
  283. margin: 0;
  284. font-size: 16px;
  285. color: #333;
  286. }
  287.  
  288. .config-toggle {
  289. font-size: 18px;
  290. color: #666;
  291. transition: transform 0.3s;
  292. }
  293.  
  294. .config-toggle.collapsed {
  295. transform: rotate(-90deg);
  296. }
  297.  
  298. .config-content {
  299. transition: opacity 0.3s;
  300. }
  301.  
  302. .config-content.collapsed {
  303. opacity: 0;
  304. height: 0;
  305. overflow: hidden;
  306. }
  307.  
  308. .config-content.expanded {
  309. opacity: 1;
  310. height: auto;
  311. }
  312.  
  313. /*
  314. UI CHANGE:
  315. Previously, only top-left resizing was available (.resize-handle-left, .resize-handle-top, .resize-handle-corner).
  316. Now we add handles on all sides + both corners (top-left & bottom-right) for a more standard resizing experience.
  317. */
  318. .resize-handle {
  319. position: absolute;
  320. background: transparent;
  321. z-index: 10;
  322. }
  323. /* Existing top-left handles */
  324. .resize-handle-left {
  325. cursor: ew-resize;
  326. width: 8px;
  327. height: 100%;
  328. left: 0;
  329. top: 0;
  330. }
  331. .resize-handle-top {
  332. cursor: ns-resize;
  333. height: 8px;
  334. width: 100%;
  335. top: 0;
  336. left: 0;
  337. }
  338. .resize-handle-corner {
  339. cursor: nwse-resize;
  340. width: 14px;
  341. height: 14px;
  342. left: 0;
  343. top: 0;
  344. }
  345.  
  346. /* NEW bottom-right corner handle */
  347. .resize-handle-corner-br {
  348. cursor: nwse-resize;
  349. width: 14px;
  350. height: 14px;
  351. right: 0;
  352. bottom: 0;
  353. }
  354. /* NEW right-only handle */
  355. .resize-handle-right {
  356. cursor: ew-resize;
  357. width: 8px;
  358. height: 100%;
  359. top: 0;
  360. right: 0;
  361. }
  362. /* NEW bottom-only handle */
  363. .resize-handle-bottom {
  364. cursor: ns-resize;
  365. height: 8px;
  366. width: 100%;
  367. bottom: 0;
  368. left: 0;
  369. }
  370.  
  371. #promptimizer-container.resizing {
  372. transition: none;
  373. box-shadow: 0 2px 15px rgba(33, 150, 243, 0.4);
  374. }
  375.  
  376. #promptimizer-container.resizing-left {
  377. border-left: 2px solid #2196F3;
  378. }
  379. #promptimizer-container.resizing-top {
  380. border-top: 2px solid #2196F3;
  381. }
  382. #promptimizer-container.resizing-corner {
  383. border-left: 2px solid #2196F3;
  384. border-top: 2px solid #2196F3;
  385. }
  386.  
  387. /* UI CHANGE: Additional highlighting classes for new handles. */
  388. #promptimizer-container.resizing-right {
  389. border-right: 2px solid #2196F3;
  390. }
  391. #promptimizer-container.resizing-bottom {
  392. border-bottom: 2px solid #2196F3;
  393. }
  394. #promptimizer-container.resizing-corner-br {
  395. border-right: 2px solid #2196F3;
  396. border-bottom: 2px solid #2196F3;
  397. }
  398. `);
  399.  
  400. // Default dimensions and position
  401. const defaultDimensions = {
  402. // UI CHANGE: Adjusted to bottom-right defaults
  403. width: '400px',
  404. height: 'auto',
  405. bottom: '80px',
  406. right: '20px'
  407. };
  408.  
  409. // Create UI elements
  410. function createUI() {
  411. // Toggle button
  412. const toggle = document.createElement('div');
  413. toggle.id = 'promptimizer-toggle';
  414. toggle.innerHTML = '✨';
  415. toggle.title = 'Toggle Promptimizer';
  416. document.body.appendChild(toggle);
  417.  
  418. // Main container
  419. const container = document.createElement('div');
  420. container.id = 'promptimizer-container';
  421. container.innerHTML = `
  422. <div id="promptimizer-header">
  423. <h2 id="promptimizer-title">Promptimizer</h2>
  424. <div id="promptimizer-header-controls">
  425. <button class="header-button" id="promptimizer-reset" title="Reset UI Position and Size">↺</button>
  426. <button class="header-button" id="promptimizer-minimize" title="Minimize">−</button>
  427. </div>
  428. </div>
  429. <div id="promptimizer-content">
  430. <div class="scrollable-flex">
  431. <div class="config-section expanded" id="config-section">
  432. <div class="config-header" id="config-header">
  433. <h3>API Configuration</h3>
  434. <span class="config-toggle">▼</span>
  435. </div>
  436. <div class="config-content expanded">
  437. <input type="text" id="api-url" class="promptimizer-input" placeholder="API URL (e.g., https://api.openai.com/v1)" />
  438. <input type="password" id="api-key" class="promptimizer-input" placeholder="API Key" />
  439. <input type="text" id="model-name" class="promptimizer-input" placeholder="Model name" />
  440. <input type="text" id="provider-name" class="promptimizer-input" placeholder="Provider name (optional, for gpt4free)" />
  441. <button id="save-config" class="promptimizer-button">Save Configuration</button>
  442. </div>
  443. </div>
  444. <div class="input-group">
  445. <label class="input-label" for="prompt-type">Prompt Type:</label>
  446. <select id="prompt-type" class="prompt-type-select">
  447. <option value="user">User Prompt</option>
  448. <option value="system">System Prompt</option>
  449. </select>
  450. </div>
  451. <div class="input-group">
  452. <label class="input-label" for="prompt-input">Enter Your Prompt:</label>
  453. <textarea id="prompt-input" class="promptimizer-input" rows="4" placeholder="Enter your prompt here..."></textarea>
  454. </div>
  455. <button id="optimize-button" class="promptimizer-button">Optimize Prompt</button>
  456. <div id="promptimizer-response-container">
  457. <button id="copy-button" style="display: none;">Copy Enhanced Prompt</button>
  458. <div id="promptimizer-response"></div>
  459. </div>
  460. </div>
  461. </div>
  462. <!-- UI CHANGE: A complete set of resize handles: top-left corner, top, left, bottom-right corner, right, bottom. -->
  463. <div class="resize-handle resize-handle-left"></div>
  464. <div class="resize-handle resize-handle-top"></div>
  465. <div class="resize-handle resize-handle-corner"></div>
  466. <div class="resize-handle resize-handle-right"></div>
  467. <div class="resize-handle resize-handle-bottom"></div>
  468. <div class="resize-handle resize-handle-corner-br"></div>
  469. `;
  470. document.body.appendChild(container);
  471.  
  472. // Make the container draggable (UI only)
  473. makeDraggable(container);
  474.  
  475. // Add container resizing
  476. makeResizable(container);
  477.  
  478. // Load saved configuration
  479. loadConfiguration();
  480.  
  481. // Event listeners
  482. // UI CHANGE: Now using a smoother show/hide approach with fade
  483. toggle.addEventListener('click', () => {
  484. if (container.style.display === 'none' || container.style.display === '') {
  485. container.style.display = 'block';
  486. // Force reflow, then add .show for fade-in
  487. requestAnimationFrame(() => container.classList.add('show'));
  488. } else {
  489. // Remove .show for fade-out
  490. container.classList.remove('show');
  491. // Wait for transition to complete, then hide
  492. setTimeout(() => {
  493. if (!container.classList.contains('show')) {
  494. container.style.display = 'none';
  495. }
  496. }, 200);
  497. }
  498. });
  499.  
  500. document.getElementById('promptimizer-minimize').addEventListener('click', () => {
  501. // Fade out on minimize
  502. container.classList.remove('show');
  503. setTimeout(() => {
  504. if (!container.classList.contains('show')) {
  505. container.style.display = 'none';
  506. }
  507. }, 200);
  508. });
  509.  
  510. // Config section toggle
  511. const configHeader = document.getElementById('config-header');
  512. const configSection = document.getElementById('config-section');
  513. const configToggle = configHeader.querySelector('.config-toggle');
  514. const configContent = configSection.querySelector('.config-content');
  515.  
  516. configHeader.addEventListener('click', () => {
  517. const isExpanded = configSection.classList.contains('expanded');
  518.  
  519. if (isExpanded) {
  520. configSection.classList.remove('expanded');
  521. configSection.classList.add('collapsed');
  522. configContent.classList.remove('expanded');
  523. configContent.classList.add('collapsed');
  524. configToggle.classList.add('collapsed');
  525. } else {
  526. configSection.classList.remove('collapsed');
  527. configSection.classList.add('expanded');
  528. configContent.classList.remove('collapsed');
  529. configContent.classList.add('expanded');
  530. configToggle.classList.remove('collapsed');
  531. }
  532. });
  533.  
  534. document.getElementById('save-config').addEventListener('click', saveConfiguration);
  535. document.getElementById('optimize-button').addEventListener('click', optimizePrompt);
  536. document.getElementById('copy-button').addEventListener('click', copyEnhancedPrompt);
  537.  
  538. // Initialize auto-height for textarea
  539. const textarea = document.getElementById('prompt-input');
  540. if (textarea) {
  541. autoResizeTextarea(textarea);
  542. textarea.addEventListener('input', function() {
  543. autoResizeTextarea(this);
  544. });
  545. }
  546. }
  547.  
  548. // Make an element draggable (UI only; no functional logic changed)
  549. function makeDraggable(element) {
  550. const header = element.querySelector('#promptimizer-header');
  551. let isDragging = false;
  552. let startX, startY, offsetX, offsetY;
  553.  
  554. // Function to reset position and size
  555. function resetPosition() {
  556. // Remove transform usage; set position explicitly
  557. element.style.width = defaultDimensions.width;
  558. element.style.height = defaultDimensions.height;
  559. element.style.bottom = defaultDimensions.bottom;
  560. element.style.right = defaultDimensions.right;
  561. element.style.top = 'auto';
  562. element.style.left = 'auto';
  563.  
  564. // Reset any set textarea dimensions
  565. const textarea = document.getElementById('prompt-input');
  566. if (textarea) {
  567. textarea.style.height = '';
  568. autoResizeTextarea(textarea);
  569. }
  570. }
  571.  
  572. // Add reset button event listener
  573. document.getElementById('promptimizer-reset').addEventListener('click', (e) => {
  574. e.stopPropagation(); // Prevent initiating a drag
  575. resetPosition();
  576. });
  577.  
  578. header.addEventListener('mousedown', dragStart);
  579. document.addEventListener('mousemove', drag);
  580. document.addEventListener('mouseup', dragEnd);
  581.  
  582. function dragStart(e) {
  583. if (e.target.closest('.header-button')) {
  584. return;
  585. }
  586. isDragging = true;
  587. // Compute how far from the element's top-left corner the pointer is
  588. const rect = element.getBoundingClientRect();
  589. startX = rect.left;
  590. startY = rect.top;
  591. offsetX = e.clientX - startX;
  592. offsetY = e.clientY - startY;
  593.  
  594. // Disable transition during actual drag for a crisper effect
  595. element.style.transition = 'none';
  596. }
  597.  
  598. function drag(e) {
  599. if (!isDragging) return;
  600. e.preventDefault();
  601.  
  602. // Figure out the new top-left based on pointer
  603. let newLeft = e.clientX - offsetX;
  604. let newTop = e.clientY - offsetY;
  605.  
  606. // Keep within viewport, if desired
  607. const rect = element.getBoundingClientRect();
  608. const w = rect.width;
  609. const h = rect.height;
  610. const vw = window.innerWidth;
  611. const vh = window.innerHeight;
  612.  
  613. // Adjust so it won't go offscreen
  614. if (newLeft < 0) newLeft = 0;
  615. if (newTop < 0) newTop = 0;
  616. if (newLeft + w > vw) newLeft = vw - w;
  617. if (newTop + h > vh) newTop = vh - h;
  618.  
  619. // Clear bottom/right so we can use top/left
  620. element.style.bottom = 'auto';
  621. element.style.right = 'auto';
  622.  
  623. // Apply new position
  624. element.style.left = newLeft + 'px';
  625. element.style.top = newTop + 'px';
  626. }
  627.  
  628. function dragEnd() {
  629. isDragging = false;
  630. // Re-enable transitions for nice layout changes after drag
  631. element.style.transition =
  632. 'opacity 0.2s ease, width 0.2s ease, height 0.2s ease, top 0.2s ease, left 0.2s ease, right 0.2s ease, bottom 0.2s ease';
  633. }
  634.  
  635. // Initialize at default bottom-right position
  636. resetPosition();
  637. }
  638.  
  639. // Make container resizable (UI only)
  640. function makeResizable(container) {
  641. const leftHandle = container.querySelector('.resize-handle-left');
  642. const topHandle = container.querySelector('.resize-handle-top');
  643. const cornerTLHandle = container.querySelector('.resize-handle-corner'); // top-left corner
  644. // UI CHANGE: Additional handles
  645. const rightHandle = container.querySelector('.resize-handle-right');
  646. const bottomHandle = container.querySelector('.resize-handle-bottom');
  647. const cornerBRHandle = container.querySelector('.resize-handle-corner-br');
  648.  
  649. let isResizing = false;
  650. let currentResizeType = '';
  651. let startX, startY, startWidth, startHeight, startTop, startLeft, startBottom, startRight;
  652.  
  653. function startResize(e, type) {
  654. e.preventDefault();
  655. e.stopPropagation();
  656.  
  657. isResizing = true;
  658. currentResizeType = type;
  659.  
  660. const rect = container.getBoundingClientRect();
  661. startWidth = rect.width;
  662. startHeight = rect.height;
  663. startTop = rect.top;
  664. startLeft = rect.left;
  665. startBottom = window.innerHeight - rect.bottom; // distance from bottom to viewport
  666. startRight = window.innerWidth - rect.right; // distance from right to viewport
  667. startX = e.clientX;
  668. startY = e.clientY;
  669.  
  670. container.classList.add('resizing');
  671. container.classList.add(`resizing-${type}`);
  672.  
  673. document.addEventListener('mousemove', resize);
  674. document.addEventListener('mouseup', stopResize);
  675.  
  676. // Disable transitions during actual resize
  677. container.style.transition = 'none';
  678. }
  679.  
  680. function resize(e) {
  681. if (!isResizing) return;
  682.  
  683. let newWidth = startWidth;
  684. let newHeight = startHeight;
  685. let newTop = startTop;
  686. let newLeft = startLeft;
  687. let newRight = startRight;
  688. let newBottom = startBottom;
  689.  
  690. const dx = e.clientX - startX;
  691. const dy = e.clientY - startY;
  692.  
  693. switch (currentResizeType) {
  694. case 'left':
  695. newWidth = startWidth - dx;
  696. newLeft = startLeft + dx;
  697. break;
  698. case 'top':
  699. newHeight = startHeight - dy;
  700. newTop = startTop + dy;
  701. break;
  702. case 'corner': // top-left corner
  703. newWidth = startWidth - dx;
  704. newHeight = startHeight - dy;
  705. newLeft = startLeft + dx;
  706. newTop = startTop + dy;
  707. break;
  708. // UI CHANGE: Additional cases for new handles
  709. case 'right':
  710. // Resizing from the right edge outward
  711. newWidth = startWidth + dx;
  712. break;
  713. case 'bottom':
  714. // Resizing from the bottom edge downward
  715. newHeight = startHeight + dy;
  716. break;
  717. case 'corner-br': // bottom-right corner
  718. newWidth = startWidth + dx;
  719. newHeight = startHeight + dy;
  720. break;
  721. }
  722.  
  723. // Enforce minimum dimensions
  724. if (newWidth < 300) {
  725. newWidth = 300;
  726. // If resizing from left or top-left corner, shift left to keep min width
  727. if (['left','corner'].includes(currentResizeType)) {
  728. newLeft = startLeft + (startWidth - 300);
  729. }
  730. // If resizing from right or bottom-right corner, we just clamp
  731. }
  732. if (newHeight < 300) {
  733. newHeight = 300;
  734. // If resizing from top or top-left corner, shift top
  735. if (['top','corner'].includes(currentResizeType)) {
  736. newTop = startTop + (startHeight - 300);
  737. }
  738. }
  739.  
  740. // Keep within viewport (optional)
  741. const vw = window.innerWidth;
  742. const vh = window.innerHeight;
  743. if (['left','corner'].includes(currentResizeType) && newLeft < 0) {
  744. newLeft = 0;
  745. }
  746. if (['top','corner'].includes(currentResizeType) && newTop < 0) {
  747. newTop = 0;
  748. }
  749. if (['right','corner-br'].includes(currentResizeType) && (startLeft + newWidth > vw)) {
  750. newWidth = vw - startLeft;
  751. }
  752. if (['bottom','corner-br'].includes(currentResizeType) && (startTop + newHeight > vh)) {
  753. newHeight = vh - startTop;
  754. }
  755.  
  756. // Apply geometry. For bottom/right-based resizing, keep the container's top/left unless
  757. // we're specifically resizing from top or left.
  758. container.style.width = newWidth + 'px';
  759. container.style.height = newHeight + 'px';
  760.  
  761. // If resizing from left or top or top-left corner, update top/left explicitly
  762. if (['left','top','corner'].includes(currentResizeType)) {
  763. container.style.left = newLeft + 'px';
  764. container.style.top = newTop + 'px';
  765. // Clear bottom/right so they won't conflict
  766. container.style.bottom = 'auto';
  767. container.style.right = 'auto';
  768. }
  769. }
  770.  
  771. function stopResize() {
  772. isResizing = false;
  773. container.classList.remove('resizing');
  774. container.classList.remove(`resizing-${currentResizeType}`);
  775.  
  776. document.removeEventListener('mousemove', resize);
  777. document.removeEventListener('mouseup', stopResize);
  778.  
  779. // Re-enable transitions
  780. container.style.transition =
  781. 'opacity 0.2s ease, width 0.2s ease, height 0.2s ease, top 0.2s ease, left 0.2s ease, right 0.2s ease, bottom 0.2s ease';
  782. }
  783.  
  784. leftHandle.addEventListener('mousedown', (e) => startResize(e, 'left'));
  785. topHandle.addEventListener('mousedown', (e) => startResize(e, 'top'));
  786. cornerTLHandle.addEventListener('mousedown', (e) => startResize(e, 'corner'));
  787. // UI CHANGE: New handles
  788. rightHandle.addEventListener('mousedown', (e) => startResize(e, 'right'));
  789. bottomHandle.addEventListener('mousedown', (e) => startResize(e, 'bottom'));
  790. cornerBRHandle.addEventListener('mousedown', (e) => startResize(e, 'corner-br'));
  791. }
  792.  
  793. // Helper function to auto-resize textarea based on content
  794. function autoResizeTextarea(textarea) {
  795. // Save scroll position
  796. const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
  797. // Reset height temporarily to get accurate scrollHeight
  798. textarea.style.height = 'auto';
  799. // Set new height based on content (with minimum)
  800. const newHeight = Math.max(textarea.scrollHeight, 100) + 'px';
  801. textarea.style.transition = 'height 0.15s ease-out';
  802. textarea.style.height = newHeight;
  803. // Restore scroll position to prevent page jump
  804. window.scrollTo(0, scrollTop);
  805. }
  806.  
  807. // Save API configuration
  808. function saveConfiguration() {
  809. const apiUrl = document.getElementById('api-url').value;
  810. const apiKey = document.getElementById('api-key').value;
  811. const modelName = document.getElementById('model-name').value;
  812. const providerName = document.getElementById('provider-name').value;
  813.  
  814. GM_setValue('apiUrl', apiUrl);
  815. GM_setValue('apiKey', apiKey);
  816. GM_setValue('modelName', modelName);
  817. GM_setValue('providerName', providerName);
  818.  
  819. showResponse('Configuration saved!');
  820. }
  821.  
  822. // Load saved configuration
  823. function loadConfiguration() {
  824. const apiUrl = GM_getValue('apiUrl', '');
  825. const apiKey = GM_getValue('apiKey', '');
  826. const modelName = GM_getValue('modelName', '');
  827. const providerName = GM_getValue('providerName', '');
  828.  
  829. document.getElementById('api-url').value = apiUrl;
  830. document.getElementById('api-key').value = apiKey;
  831. document.getElementById('model-name').value = modelName;
  832. document.getElementById('provider-name').value = providerName;
  833. }
  834.  
  835. // Show response or error message
  836. function showResponse(message, isError = false) {
  837. const responseDiv = document.getElementById('promptimizer-response');
  838. const copyButton = document.getElementById('copy-button');
  839. responseDiv.textContent = message;
  840. responseDiv.className = isError ? 'promptimizer-error' : '';
  841.  
  842. if (!isError && message !== 'Optimizing prompt...') {
  843. copyButton.style.display = 'block';
  844. setTimeout(() => copyButton.classList.add('visible'), 10);
  845. } else {
  846. copyButton.style.display = 'none';
  847. copyButton.classList.remove('visible');
  848. }
  849. }
  850.  
  851. // Extract enhanced prompt from response
  852. function extractEnhancedPrompt(text) {
  853. const match = text.match(/<enhanced_prompt>([\s\S]*?)<\/enhanced_prompt>/);
  854. return match ? match[1].trim() : text;
  855. }
  856.  
  857. // Copy enhanced prompt to clipboard
  858. function copyEnhancedPrompt() {
  859. const responseDiv = document.getElementById('promptimizer-response');
  860. const copyButton = document.getElementById('copy-button');
  861. const textToCopy = extractEnhancedPrompt(responseDiv.textContent);
  862.  
  863. navigator.clipboard.writeText(textToCopy).then(() => {
  864. copyButton.textContent = 'Copied!';
  865. copyButton.classList.add('copied');
  866. setTimeout(() => {
  867. copyButton.textContent = 'Copy Enhanced Prompt';
  868. copyButton.classList.remove('copied');
  869. }, 2000);
  870. }).catch(err => {
  871. console.error('Failed to copy text: ', err);
  872. });
  873. }
  874.  
  875. // Optimize prompt using the API
  876. async function optimizePrompt() {
  877. const apiUrl = GM_getValue('apiUrl', '');
  878. const apiKey = GM_getValue('apiKey', '');
  879. const modelName = GM_getValue('modelName', '');
  880. const providerName = GM_getValue('providerName', '');
  881. const promptType = document.getElementById('prompt-type').value;
  882. const promptInput = document.getElementById('prompt-input').value;
  883.  
  884. if (!apiUrl || !apiKey || !modelName) {
  885. showResponse('Please configure API settings first!', true);
  886. return;
  887. }
  888.  
  889. if (!promptInput.trim()) {
  890. showResponse('Please enter a prompt to optimize!', true);
  891. return;
  892. }
  893.  
  894. showResponse('Optimizing prompt...');
  895.  
  896. const systemPrompt = `You are a specialized prompt optimization AI focused on enhancing both user prompts and system prompts for AI interactions.
  897.  
  898. <instructions>
  899. 1. You will receive prompts marked with either <user_prompt> or <system_prompt> tags
  900. 2. ALWAYS maintain the same type of prompt in your enhancement
  901. 3. NEVER engage in conversation or provide explanations
  902. 4. ONLY return the enhanced prompt within <enhanced_prompt> tags
  903. 5. Apply appropriate prompt engineering techniques based on the prompt type:
  904.  
  905. For User Prompts:
  906. - Maintain conversational context and flow
  907. - Clarify user intent and expectations
  908. - Add specific parameters for the response
  909. - Include relevant context from prior conversation
  910. - Structure multi-part requests clearly
  911.  
  912. For System Prompts:
  913. - Define clear roles and responsibilities
  914. - Establish behavioral boundaries
  915. - Include success criteria and constraints
  916. - Structure hierarchical instructions
  917. - Define interaction patterns
  918. - Specify output formats and preferences
  919. - Include error handling instructions
  920. </instructions>
  921.  
  922. <examples>
  923. <example>
  924. <input_type>user_prompt</input_type>
  925. <input>Thanks for your help! Can you search the web for more information about this topic?</input>
  926. <enhanced_prompt>Please conduct a comprehensive web search on our current topic with the following parameters:
  927. 1. Focus on authoritative sources from the last 2 years
  928. 2. Include academic and expert perspectives
  929. 3. Compare and contrast different viewpoints
  930. 4. Identify emerging trends and developments
  931. 5. Extract key insights and practical applications
  932.  
  933. Format the response with:
  934. - Main findings in bullet points
  935. - Source citations for each major claim
  936. - Relevance assessment for each source
  937. - Synthesis of conflicting information
  938. - Suggestions for further research</enhanced_prompt>
  939. </example>
  940.  
  941. <example>
  942. <input_type>system_prompt</input_type>
  943. <input>You are a web search AI assistant. Your role is to help users find information.</input>
  944. <enhanced_prompt>You are a specialized web search AI assistant designed to provide comprehensive, accurate, and well-structured information retrieval services.
  945.  
  946. Core Responsibilities:
  947. 1. Execute precise web searches based on user queries
  948. 2. Evaluate source credibility and relevance
  949. 3. Synthesize information from multiple sources
  950. 4. Present findings in clear, structured formats
  951.  
  952. Behavioral Guidelines:
  953. - Maintain objectivity in information presentation
  954. - Clearly distinguish between facts and interpretations
  955. - Acknowledge information gaps or uncertainties
  956. - Proactively suggest related topics for exploration
  957.  
  958. Output Requirements:
  959. 1. Structure all responses with:
  960. - Executive summary
  961. - Detailed findings
  962. - Source citations
  963. - Confidence levels
  964. 2. Use formatting for clarity:
  965. - Bullet points for key facts
  966. - Tables for comparisons
  967. - Markdown for emphasis
  968. - Hierarchical headings
  969.  
  970. Error Handling:
  971. - Acknowledge when information is outdated
  972. - Flag potential misinformation
  973. - Suggest alternative search strategies
  974. - Provide confidence levels for findings</enhanced_prompt>
  975. </example>
  976. </examples>
  977.  
  978. <success_criteria>
  979. For User Prompts:
  980. - Enhanced clarity and specificity
  981. - Maintained conversation context
  982. - Clear parameters for response
  983. - Structured multi-part requests
  984. - Defined output preferences
  985.  
  986. For System Prompts:
  987. - Clear role definition
  988. - Comprehensive behavioral guidelines
  989. - Specific output requirements
  990. - Error handling procedures
  991. - Interaction patterns defined
  992. </success_criteria>`;
  993.  
  994. // Wrap the input with appropriate type tags
  995. const taggedInput = `<${promptType}_prompt>${promptInput}</${promptType}_prompt>`;
  996.  
  997. GM_xmlhttpRequest({
  998. method: 'POST',
  999. url: `${apiUrl}/chat/completions`,
  1000. headers: {
  1001. 'Content-Type': 'application/json',
  1002. 'Authorization': `Bearer ${apiKey}`
  1003. },
  1004. data: JSON.stringify({
  1005. model: modelName,
  1006. ...(providerName && { provider: providerName }),
  1007. messages: [
  1008. {
  1009. role: 'system',
  1010. content: systemPrompt
  1011. },
  1012. {
  1013. role: 'user',
  1014. content: taggedInput
  1015. }
  1016. ],
  1017. temperature: 0.7,
  1018. max_tokens: 16384
  1019. }),
  1020. onload: function(response) {
  1021. try {
  1022. const result = JSON.parse(response.responseText);
  1023. if (result.error) {
  1024. showResponse(`Error: ${result.error.message}`, true);
  1025. } else if (result.choices && result.choices[0]) {
  1026. showResponse(result.choices[0].message.content);
  1027. } else {
  1028. showResponse('Unexpected API response format', true);
  1029. }
  1030. } catch (error) {
  1031. showResponse(`Error processing response: ${error.message}`, true);
  1032. }
  1033. },
  1034. onerror: function(error) {
  1035. showResponse(`Network error: ${error.statusText}`, true);
  1036. }
  1037. });
  1038. }
  1039.  
  1040. // Initialize the UI
  1041. createUI();
  1042. })();