AI提示词助手

AI提示词管理和快速输入工具

  1. // ==UserScript==
  2. // @name AI提示词助手
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.2
  5. // @description AI提示词管理和快速输入工具
  6. // @author 初沐 (https://github.com/chumu)
  7. // @license MIT
  8. // @match https://pro.yrai.cc/new
  9. // @grant GM_addStyle
  10. // @grant GM_download
  11. // @grant GM_xmlhttpRequest
  12. // @connect cdn.jsdelivr.net
  13. // @run-at document-end
  14. // ==/UserScript==
  15.  
  16. (function() {
  17. 'use strict';
  18.  
  19. // 首先加载xlsx库
  20. function loadXLSX() {
  21. return new Promise((resolve, reject) => {
  22. GM_xmlhttpRequest({
  23. method: 'GET',
  24. url: 'https://cdn.jsdelivr.net/npm/xlsx/dist/xlsx.full.min.js',
  25. onload: function(response) {
  26. try {
  27. // 创建script标签并执行代码
  28. const script = document.createElement('script');
  29. script.textContent = response.responseText;
  30. document.head.appendChild(script);
  31. resolve();
  32. } catch (error) {
  33. reject(error);
  34. }
  35. },
  36. onerror: function(error) {
  37. reject(error);
  38. }
  39. });
  40. });
  41. }
  42.  
  43. // 等待xlsx库加载完成后初始化UI
  44. loadXLSX().then(() => {
  45. // 原有的初始化代码
  46. createUI();
  47. }).catch(error => {
  48. console.error('加载xlsx库失败:', error);
  49. // 即使加载失败也创建UI,只是Excel功能不可用
  50. createUI();
  51. });
  52.  
  53. // 添加样式
  54. GM_addStyle(`
  55. .prompt-helper {
  56. position: fixed;
  57. right: 20px;
  58. top: 20px;
  59. background: white;
  60. border: 1px solid #ccc;
  61. padding: 20px;
  62. border-radius: 12px;
  63. box-shadow: 0 4px 20px rgba(0,0,0,0.15);
  64. z-index: 9999;
  65. width: 380px;
  66. font-family: Arial, sans-serif;
  67. transition: transform 0.3s ease;
  68. }
  69. .prompt-helper.collapsed {
  70. transform: translateX(calc(100% + 20px));
  71. }
  72. .toggle-button {
  73. position: fixed;
  74. right: 20px;
  75. top: 20px;
  76. z-index: 9998;
  77. padding: 8px 15px;
  78. background: #4a90e2;
  79. color: white;
  80. border: none;
  81. border-radius: 6px;
  82. cursor: pointer;
  83. box-shadow: 0 2px 8px rgba(0,0,0,0.1);
  84. }
  85. .toggle-button:hover {
  86. background: #357abd;
  87. }
  88. .manage-categories {
  89. margin-top: 10px;
  90. padding: 10px;
  91. background: #f9f9f9;
  92. border-radius: 8px;
  93. display: none;
  94. }
  95. .manage-categories.active {
  96. display: block;
  97. }
  98. .category-list {
  99. margin: 10px 0;
  100. padding: 5px;
  101. background: white;
  102. border: 1px solid #eee;
  103. border-radius: 6px;
  104. max-height: 150px;
  105. overflow-y: auto;
  106. }
  107. .category-item {
  108. display: flex;
  109. justify-content: space-between;
  110. align-items: center;
  111. padding: 8px;
  112. border-bottom: 1px solid #eee;
  113. }
  114. .category-item:last-child {
  115. border-bottom: none;
  116. }
  117. .prompt-helper h3 {
  118. margin: 0 0 15px 0;
  119. color: #333;
  120. font-size: 18px;
  121. border-bottom: 2px solid #eee;
  122. padding-bottom: 10px;
  123. display: flex;
  124. align-items: center;
  125. justify-content: space-between;
  126. }
  127. .prompt-helper h3 .title {
  128. flex: 1;
  129. }
  130. .prompt-helper h3 .buttons {
  131. display: flex;
  132. gap: 8px;
  133. align-items: center;
  134. }
  135. .prompt-helper h3 button {
  136. padding: 4px 12px;
  137. border: 1px solid #ddd;
  138. border-radius: 4px;
  139. background: #f5f5f5;
  140. cursor: pointer;
  141. font-size: 14px;
  142. line-height: 1.5;
  143. transition: all 0.2s;
  144. }
  145. .prompt-helper h3 button:hover {
  146. background: #e5e5e5;
  147. }
  148. .prompt-helper button {
  149. margin: 5px;
  150. padding: 6px 12px;
  151. border: 1px solid #ddd;
  152. border-radius: 6px;
  153. background: #f5f5f5;
  154. cursor: pointer;
  155. transition: all 0.2s;
  156. }
  157. .prompt-helper button:hover {
  158. background: #e5e5e5;
  159. transform: translateY(-1px);
  160. }
  161. .prompt-helper input, .prompt-helper select, .prompt-helper textarea {
  162. padding: 6px 10px;
  163. border: 1px solid #ddd;
  164. border-radius: 6px;
  165. margin-bottom: 8px;
  166. }
  167. .prompt-list {
  168. margin-top: 15px;
  169. max-height: 400px;
  170. overflow-y: auto;
  171. }
  172. .prompt-item {
  173. margin: 8px 0;
  174. padding: 10px;
  175. border: 1px solid #eee;
  176. border-radius: 8px;
  177. background: #fafafa;
  178. }
  179. .prompt-item:hover {
  180. border-color: #ddd;
  181. background: #fff;
  182. box-shadow: 0 2px 8px rgba(0,0,0,0.05);
  183. }
  184. .prompt-content {
  185. margin: 5px 0;
  186. font-size: 14px;
  187. color: #666;
  188. }
  189. .prompt-comment {
  190. font-size: 12px;
  191. color: #888;
  192. margin-top: 5px;
  193. font-style: italic;
  194. }
  195. .category-section {
  196. margin: 15px 0;
  197. padding: 10px;
  198. border: 1px solid #eee;
  199. border-radius: 8px;
  200. }
  201. .category-title {
  202. font-weight: bold;
  203. color: #444;
  204. margin-bottom: 10px;
  205. }
  206. .search-box {
  207. width: 100%;
  208. margin-bottom: 15px;
  209. }
  210. .controls {
  211. display: flex;
  212. gap: 8px;
  213. margin-bottom: 10px;
  214. }
  215. .add-prompt-form {
  216. display: none;
  217. padding: 10px;
  218. background: #f9f9f9;
  219. border-radius: 8px;
  220. margin: 10px 0;
  221. }
  222. .add-prompt-form.active {
  223. display: block;
  224. }
  225. #addCategory {
  226. font-size: 12px;
  227. padding: 4px 8px;
  228. }
  229. #newCategory {
  230. font-size: 12px;
  231. }
  232. .import-section {
  233. margin: 10px 0;
  234. padding: 10px;
  235. background: #f9f9f9;
  236. border-radius: 8px;
  237. border: 2px dashed #ccc;
  238. text-align: center;
  239. transition: all 0.3s;
  240. overflow: hidden;
  241. }
  242. .import-section.collapsed {
  243. padding: 0;
  244. height: 0;
  245. border: none;
  246. margin: 0;
  247. }
  248. .import-section.dragover {
  249. background: #e9e9e9;
  250. border-color: #4a90e2;
  251. }
  252. .import-toggle {
  253. display: flex;
  254. align-items: center;
  255. padding: 8px;
  256. background: #f5f5f5;
  257. border-radius: 4px;
  258. cursor: pointer;
  259. user-select: none;
  260. margin-bottom: 10px;
  261. transition: all 0.3s;
  262. }
  263. .import-toggle:hover {
  264. background: #e8e8e8;
  265. }
  266. .import-toggle .arrow {
  267. width: 0;
  268. height: 0;
  269. border-left: 5px solid transparent;
  270. border-right: 5px solid transparent;
  271. border-top: 5px solid #666;
  272. margin-right: 8px;
  273. transition: transform 0.3s;
  274. }
  275. .import-toggle.collapsed .arrow {
  276. transform: rotate(-90deg);
  277. }
  278. .import-toggle-text {
  279. color: #666;
  280. font-size: 14px;
  281. }
  282. #fileInput {
  283. display: none;
  284. }
  285. .display-mode-switch {
  286. display: flex;
  287. align-items: center;
  288. gap: 8px;
  289. margin: 10px 0;
  290. padding: 8px;
  291. background: #f5f5f5;
  292. border-radius: 6px;
  293. }
  294. .display-mode-switch label {
  295. font-size: 14px;
  296. color: #666;
  297. }
  298. .prompt-content.simplified {
  299. max-height: 1.5em;
  300. overflow: hidden;
  301. text-overflow: ellipsis;
  302. white-space: nowrap;
  303. cursor: pointer;
  304. }
  305. .prompt-content.simplified:hover {
  306. color: #4a90e2;
  307. }
  308. .settings-menu {
  309. display: none;
  310. position: absolute;
  311. top: 50px;
  312. right: 20px;
  313. background: white;
  314. border: 1px solid #ddd;
  315. border-radius: 8px;
  316. padding: 15px;
  317. box-shadow: 0 2px 10px rgba(0,0,0,0.1);
  318. z-index: 10001;
  319. width: 250px;
  320. }
  321. .settings-menu.active {
  322. display: block;
  323. }
  324. .settings-menu h4 {
  325. margin: 0 0 10px 0;
  326. padding-bottom: 8px;
  327. border-bottom: 1px solid #eee;
  328. color: #333;
  329. }
  330. .settings-item {
  331. margin: 8px 0;
  332. }
  333. .settings-item label {
  334. display: flex;
  335. align-items: center;
  336. gap: 8px;
  337. color: #666;
  338. font-size: 14px;
  339. }
  340. .settings-item input[type="checkbox"] {
  341. margin: 0;
  342. }
  343. .prompt-item {
  344. position: relative;
  345. }
  346. .prompt-name {
  347. font-weight: bold;
  348. margin-bottom: 5px;
  349. }
  350. .prompt-content {
  351. margin: 5px 0;
  352. color: #333;
  353. }
  354. .prompt-content.simplified {
  355. max-height: 1.5em;
  356. overflow: hidden;
  357. text-overflow: ellipsis;
  358. white-space: nowrap;
  359. cursor: pointer;
  360. }
  361. .prompt-comment {
  362. font-size: 12px;
  363. color: #666;
  364. font-style: italic;
  365. margin-top: 3px;
  366. }
  367. .prompt-category {
  368. font-size: 12px;
  369. color: #4a90e2;
  370. margin-top: 3px;
  371. }
  372. .settings-menu h4 {
  373. margin: 0 0 10px 0;
  374. padding-bottom: 8px;
  375. border-bottom: 1px solid #eee;
  376. color: #333;
  377. }
  378. .settings-menu .author-info {
  379. margin-top: 15px;
  380. padding-top: 15px;
  381. border-top: 1px solid #eee;
  382. font-size: 13px;
  383. color: #666;
  384. }
  385. .settings-menu .author-info p {
  386. margin: 5px 0;
  387. line-height: 1.4;
  388. }
  389. .settings-menu .author-info a {
  390. color: #4a90e2;
  391. text-decoration: none;
  392. }
  393. .settings-menu .author-info a:hover {
  394. text-decoration: underline;
  395. }
  396. `);
  397.  
  398. // 获取保存的分类
  399. function getCategories() {
  400. return JSON.parse(localStorage.getItem('aiPromptCategories') || '["洗稿", "创作", "翻译"]');
  401. }
  402.  
  403. // 保存分类
  404. function saveCategories(categories) {
  405. localStorage.setItem('aiPromptCategories', JSON.stringify(categories));
  406. }
  407.  
  408. // 更新分类列表
  409. function updateCategoryList() {
  410. const categoryList = document.getElementById('categoryList');
  411. const categories = getCategories();
  412. categoryList.innerHTML = categories.map(category => `
  413. <div class="category-item" data-category="${category}">
  414. <span class="category-name">${category}</span>
  415. <button class="btn btn-danger" onclick="deleteCategory('${category}')">删除</button>
  416. </div>
  417. `).join('');
  418. }
  419.  
  420. // 删除分类
  421. function deleteCategory(name) {
  422. if (confirm(`确定要删除分类"${name}"吗?`)) {
  423. const categories = getCategories();
  424. const newCategories = categories.filter(c => c !== name);
  425. saveCategories(newCategories);
  426. updateCategoryList();
  427. updateCategorySelectors();
  428. }
  429. }
  430.  
  431. // 清空表单
  432. function clearForm() {
  433. document.getElementById('promptName').value = '';
  434. document.getElementById('promptContent').value = '';
  435. document.getElementById('promptComment').value = '';
  436. }
  437.  
  438. // 保存提示词
  439. function savePrompt(name, content, comment, category) {
  440. const prompts = JSON.parse(localStorage.getItem('aiPrompts') || '{}');
  441. prompts[name] = {
  442. content: content,
  443. comment: comment || '',
  444. category: category
  445. };
  446. localStorage.setItem('aiPrompts', JSON.stringify(prompts));
  447. }
  448.  
  449. // 删除提示词
  450. function deletePrompt(name) {
  451. const prompts = JSON.parse(localStorage.getItem('aiPrompts') || '{}');
  452. if (prompts[name]) {
  453. delete prompts[name];
  454. localStorage.setItem('aiPrompts', JSON.stringify(prompts));
  455. loadPrompts();
  456. showToast('提示词已删除');
  457. }
  458. }
  459.  
  460. // 复制提示词内容
  461. function copyPromptContent(content) {
  462. // 确保content不为空
  463. if (!content) {
  464. console.error('复制内容为空');
  465. return;
  466. }
  467.  
  468. // 使用 navigator.clipboard API
  469. if (navigator.clipboard && window.isSecureContext) {
  470. navigator.clipboard.writeText(content).then(() => {
  471. showToast('复制成功');
  472. }).catch((err) => {
  473. console.error('Clipboard API failed:', err);
  474. fallbackCopy(content);
  475. });
  476. } else {
  477. fallbackCopy(content);
  478. }
  479. }
  480.  
  481. // 回退的复制方法
  482. function fallbackCopy(content) {
  483. const textarea = document.createElement('textarea');
  484. textarea.value = content;
  485. textarea.style.position = 'fixed';
  486. textarea.style.left = '-9999px';
  487. textarea.style.top = '0';
  488. document.body.appendChild(textarea);
  489. textarea.focus();
  490. textarea.select();
  491.  
  492. try {
  493. document.execCommand('copy');
  494. showToast('复制成功');
  495. } catch (err) {
  496. console.error('复制失败:', err);
  497. showToast('复制失败');
  498. }
  499.  
  500. document.body.removeChild(textarea);
  501. }
  502.  
  503. // 显示提示信息
  504. function showToast(message) {
  505. const toast = document.createElement('div');
  506. toast.style.cssText = `
  507. position: fixed;
  508. bottom: 20px;
  509. left: 50%;
  510. transform: translateX(-50%);
  511. background: rgba(0, 0, 0, 0.7);
  512. color: white;
  513. padding: 8px 16px;
  514. border-radius: 4px;
  515. font-size: 14px;
  516. z-index: 10000;
  517. `;
  518. toast.textContent = message;
  519. document.body.appendChild(toast);
  520. setTimeout(() => document.body.removeChild(toast), 2000);
  521. }
  522.  
  523. // 获取显示设置
  524. function getDisplaySettings() {
  525. return JSON.parse(localStorage.getItem('promptDisplaySettings') || JSON.stringify({
  526. showName: true,
  527. showContent: true,
  528. showComment: true,
  529. showCategory: true,
  530. isSimplified: false
  531. }));
  532. }
  533.  
  534. // 保存显示设置
  535. function saveDisplaySettings(settings) {
  536. localStorage.setItem('promptDisplaySettings', JSON.stringify(settings));
  537. }
  538.  
  539. // 加载提示词
  540. function loadPrompts(searchTerm = '') {
  541. const promptList = document.getElementById('promptList');
  542. const prompts = JSON.parse(localStorage.getItem('aiPrompts') || '{}');
  543. const selectedCategory = document.getElementById('categorySelect').value;
  544. const settings = getDisplaySettings();
  545. promptList.innerHTML = '';
  546. // 按分类组织提示词
  547. const categorizedPrompts = {};
  548. Object.entries(prompts).forEach(([name, data]) => {
  549. if (searchTerm &&
  550. !name.toLowerCase().includes(searchTerm.toLowerCase()) &&
  551. !data.content.toLowerCase().includes(searchTerm.toLowerCase())) {
  552. return;
  553. }
  554. if (selectedCategory !== 'all' && data.category !== selectedCategory) {
  555. return;
  556. }
  557.  
  558. if (!categorizedPrompts[data.category]) {
  559. categorizedPrompts[data.category] = [];
  560. }
  561. categorizedPrompts[data.category].push({name, ...data});
  562. });
  563.  
  564. Object.entries(categorizedPrompts).forEach(([category, items]) => {
  565. const categorySection = document.createElement('div');
  566. categorySection.className = 'category-section';
  567. categorySection.innerHTML = `<div class="category-title">${category}</div>`;
  568.  
  569. items.forEach(item => {
  570. const div = document.createElement('div');
  571. div.className = 'prompt-item';
  572. // 根据设置显示内容
  573. if (settings.showName) {
  574. const nameDiv = document.createElement('div');
  575. nameDiv.className = 'prompt-name';
  576. nameDiv.textContent = item.name;
  577. div.appendChild(nameDiv);
  578. }
  579. if (settings.showContent) {
  580. const contentDiv = document.createElement('div');
  581. contentDiv.className = `prompt-content ${settings.isSimplified ? 'simplified' : ''}`;
  582. contentDiv.textContent = item.content;
  583. if (settings.isSimplified) {
  584. contentDiv.title = item.content;
  585. contentDiv.addEventListener('click', function() {
  586. this.classList.toggle('simplified');
  587. });
  588. }
  589. div.appendChild(contentDiv);
  590. }
  591. if (settings.showComment && item.comment) {
  592. const commentDiv = document.createElement('div');
  593. commentDiv.className = 'prompt-comment';
  594. commentDiv.textContent = `注释: ${item.comment}`;
  595. div.appendChild(commentDiv);
  596. }
  597.  
  598. if (settings.showCategory) {
  599. const categoryDiv = document.createElement('div');
  600. categoryDiv.className = 'prompt-category';
  601. categoryDiv.textContent = `分类: ${item.category}`;
  602. div.appendChild(categoryDiv);
  603. }
  604. // 创建按钮容器
  605. const buttonDiv = document.createElement('div');
  606. buttonDiv.style.textAlign = 'right';
  607. // 创建复制按钮
  608. const copyButton = document.createElement('button');
  609. copyButton.textContent = '复制';
  610. copyButton.dataset.content = item.content;
  611. copyButton.addEventListener('click', function() {
  612. copyPromptContent(this.dataset.content);
  613. });
  614. // 创建删除按钮
  615. const deleteButton = document.createElement('button');
  616. deleteButton.textContent = '删除';
  617. deleteButton.dataset.name = item.name;
  618. deleteButton.addEventListener('click', function() {
  619. deletePrompt(this.dataset.name);
  620. });
  621. buttonDiv.appendChild(copyButton);
  622. buttonDiv.appendChild(deleteButton);
  623. div.appendChild(buttonDiv);
  624. categorySection.appendChild(div);
  625. });
  626.  
  627. promptList.appendChild(categorySection);
  628. });
  629. }
  630.  
  631. // 初始化事件
  632. function initEvents() {
  633. // 搜索功能
  634. document.getElementById('searchPrompt').addEventListener('input', function(e) {
  635. loadPrompts(e.target.value);
  636. });
  637.  
  638. // 分类筛选
  639. document.getElementById('categorySelect').addEventListener('change', function() {
  640. loadPrompts(document.getElementById('searchPrompt').value);
  641. });
  642.  
  643. // 新增提示词表单显示/隐藏
  644. document.getElementById('addNewPrompt').addEventListener('click', function() {
  645. document.getElementById('addPromptForm').classList.add('active');
  646. });
  647.  
  648. document.getElementById('cancelPrompt').addEventListener('click', function() {
  649. document.getElementById('addPromptForm').classList.remove('active');
  650. clearForm();
  651. });
  652.  
  653. // 保存提示词
  654. document.getElementById('savePrompt').addEventListener('click', function() {
  655. const name = document.getElementById('promptName').value;
  656. const content = document.getElementById('promptContent').value;
  657. const comment = document.getElementById('promptComment').value;
  658. const category = document.getElementById('promptCategory').value;
  659.  
  660. if (name && content) {
  661. savePrompt(name, content, comment, category);
  662. clearForm();
  663. document.getElementById('addPromptForm').classList.remove('active');
  664. loadPrompts();
  665. }
  666. });
  667.  
  668. // 管理分类按钮
  669. document.getElementById('manageCategories').addEventListener('click', function() {
  670. document.getElementById('manageCategoriesForm').classList.toggle('active');
  671. });
  672.  
  673. // 添加新分类
  674. document.getElementById('addCategory').addEventListener('click', function() {
  675. const newCategoryInput = document.getElementById('newCategory');
  676. const newCategory = newCategoryInput.value.trim();
  677. if (newCategory && !getCategories().includes(newCategory)) {
  678. const categories = getCategories();
  679. categories.push(newCategory);
  680. saveCategories(categories);
  681. updateCategorySelectors();
  682. updateCategoryList();
  683. newCategoryInput.value = '';
  684. }
  685. });
  686.  
  687. // 窗口控制
  688. document.getElementById('toggleHelper').addEventListener('click', function() {
  689. const helper = document.querySelector('.prompt-helper');
  690. const toggleButton = document.querySelector('.toggle-button');
  691. helper.classList.toggle('collapsed');
  692. toggleButton.style.display = helper.classList.contains('collapsed') ? 'block' : 'none';
  693. });
  694.  
  695. // 为promptList添加事件委托
  696. document.getElementById('promptList').addEventListener('click', function(e) {
  697. if (e.target.tagName === 'BUTTON') {
  698. if (e.target.textContent === '复制') {
  699. copyPromptContent(decodeURIComponent(e.target.getAttribute('data-content')));
  700. } else if (e.target.textContent === '删除') {
  701. deletePrompt(decodeURIComponent(e.target.getAttribute('data-name')));
  702. }
  703. }
  704. });
  705.  
  706. // 导入文件按钮点击事件
  707. document.getElementById('importBtn').addEventListener('click', function() {
  708. document.getElementById('fileInput').click();
  709. });
  710.  
  711. // 文件选择变化事件
  712. document.getElementById('fileInput').addEventListener('change', handleFileImport);
  713.  
  714. // 下载模板按钮点击事件
  715. document.getElementById('downloadTemplate').addEventListener('click', downloadTemplate);
  716.  
  717. // 添加拖拽相关事件
  718. const importSection = document.querySelector('.import-section');
  719. ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
  720. importSection.addEventListener(eventName, preventDefaults, false);
  721. document.body.addEventListener(eventName, preventDefaults, false);
  722. });
  723.  
  724. function preventDefaults(e) {
  725. e.preventDefault();
  726. e.stopPropagation();
  727. }
  728.  
  729. ['dragenter', 'dragover'].forEach(eventName => {
  730. importSection.addEventListener(eventName, highlight, false);
  731. });
  732.  
  733. ['dragleave', 'drop'].forEach(eventName => {
  734. importSection.addEventListener(eventName, unhighlight, false);
  735. });
  736.  
  737. function highlight(e) {
  738. importSection.classList.add('dragover');
  739. }
  740.  
  741. function unhighlight(e) {
  742. importSection.classList.remove('dragover');
  743. }
  744.  
  745. importSection.addEventListener('drop', handleDrop, false);
  746.  
  747. function handleDrop(e) {
  748. const dt = e.dataTransfer;
  749. const files = dt.files;
  750. if (files.length) {
  751. handleFileImport({ target: { files: [files[0]] } });
  752. }
  753. }
  754.  
  755. // 设置按钮点击事件
  756. document.getElementById('settingsBtn').addEventListener('click', function(e) {
  757. e.stopPropagation();
  758. document.getElementById('settingsMenu').classList.toggle('active');
  759. });
  760.  
  761. // 点击其他地方关闭设置菜单
  762. document.addEventListener('click', function(e) {
  763. const settingsMenu = document.getElementById('settingsMenu');
  764. if (!settingsMenu.contains(e.target) && e.target.id !== 'settingsBtn') {
  765. settingsMenu.classList.remove('active');
  766. }
  767. });
  768.  
  769. // 设置选项变化事件
  770. ['showName', 'showContent', 'showComment', 'showCategory', 'isSimplified'].forEach(id => {
  771. document.getElementById(id).addEventListener('change', function() {
  772. const settings = getDisplaySettings();
  773. settings[id] = this.checked;
  774. saveDisplaySettings(settings);
  775. loadPrompts(document.getElementById('searchPrompt').value);
  776. });
  777. });
  778.  
  779. // 导入区域显示/隐藏
  780. document.getElementById('importToggle').addEventListener('click', function() {
  781. const importSection = document.getElementById('importSection');
  782. const importToggle = document.getElementById('importToggle');
  783. importSection.classList.toggle('collapsed');
  784. importToggle.classList.toggle('collapsed');
  785. });
  786. }
  787.  
  788. // 更新分类选择器
  789. function updateCategorySelectors() {
  790. const categories = getCategories();
  791. const selectors = document.querySelectorAll('#categorySelect, #promptCategory');
  792. selectors.forEach(selector => {
  793. const currentValue = selector.value;
  794. selector.innerHTML = selector.id === 'categorySelect' ?
  795. '<option value="all">所有分类</option>' : '';
  796. categories.forEach(category => {
  797. selector.innerHTML += `<option value="${category}">${category}</option>`;
  798. });
  799. if (currentValue && categories.includes(currentValue)) {
  800. selector.value = currentValue;
  801. }
  802. });
  803. }
  804.  
  805. // 下载Excel模板
  806. function downloadTemplate() {
  807. const template = [
  808. ['名称', '内容', '注释', '分类'],
  809. ['示例1', '这是一个示例提示词', '这是注释', '创作'],
  810. ['示例2', '这是另一个示例', '可选的注释', '洗稿']
  811. ];
  812.  
  813. // 创建工作簿
  814. const wb = XLSX.utils.book_new();
  815. const ws = XLSX.utils.aoa_to_sheet(template);
  816. XLSX.utils.book_append_sheet(wb, ws, "提示词");
  817.  
  818. // 生成Excel文件并下载
  819. XLSX.writeFile(wb, '提示词模板.xlsx');
  820. }
  821.  
  822. // 处理导入的文件
  823. function handleFileImport(event) {
  824. const file = event.target.files[0];
  825. if (!file) return;
  826.  
  827. const fileExtension = file.name.split('.').pop().toLowerCase();
  828.  
  829. if (fileExtension === 'xlsx' || fileExtension === 'xls') {
  830. // 处理Excel文件
  831. const reader = new FileReader();
  832. reader.onload = function(e) {
  833. try {
  834. const data = new Uint8Array(e.target.result);
  835. const workbook = XLSX.read(data, {type: 'array'});
  836. const firstSheetName = workbook.SheetNames[0];
  837. const worksheet = workbook.Sheets[firstSheetName];
  838. // 转换为数组格式
  839. const rows = XLSX.utils.sheet_to_json(worksheet, {header: 1});
  840. if (rows.length < 2) {
  841. showToast('Excel文件内容为空');
  842. return;
  843. }
  844.  
  845. const prompts = JSON.parse(localStorage.getItem('aiPrompts') || '{}');
  846. let importCount = 0;
  847.  
  848. // 跳过标题行
  849. for (let i = 1; i < rows.length; i++) {
  850. const row = rows[i];
  851. if (!row || row.length === 0) continue;
  852.  
  853. const [name, content, comment, category] = row;
  854. if (name && content) {
  855. prompts[name] = {
  856. content: content,
  857. comment: comment || '',
  858. category: category || '其他'
  859. };
  860. importCount++;
  861. }
  862. }
  863.  
  864. localStorage.setItem('aiPrompts', JSON.stringify(prompts));
  865. showToast(`成功导入 ${importCount} 个提示词`);
  866. loadPrompts();
  867. } catch (err) {
  868. console.error('Excel解析失败:', err);
  869. showToast('Excel文件格式错误,请检查格式是否正确');
  870. }
  871. };
  872. reader.readAsArrayBuffer(file);
  873. } else if (fileExtension === 'csv') {
  874. // 处理CSV文件的现有代码
  875. handleCSVImport(file);
  876. } else {
  877. showToast('不支持的文件格式,请使用.xlsx、.xls或.csv格式');
  878. }
  879. }
  880.  
  881. // 处理CSV文件导入
  882. function handleCSVImport(file) {
  883. // 尝试不同的编码
  884. function tryReadFile(encoding) {
  885. return new Promise((resolve, reject) => {
  886. const reader = new FileReader();
  887. reader.onload = function(e) {
  888. try {
  889. const text = e.target.result;
  890. // 检查是否包含乱码字符
  891. if (text.includes('�')) {
  892. reject('encoding_error');
  893. return;
  894. }
  895. resolve(text);
  896. } catch (err) {
  897. reject(err);
  898. }
  899. };
  900. reader.onerror = reject;
  901. reader.readAsText(file, encoding);
  902. });
  903. }
  904.  
  905. // 按顺序尝试不同的编码
  906. const encodings = ['UTF-8', 'GBK', 'GB2312', 'BIG5'];
  907. let currentEncodingIndex = 0;
  908.  
  909. function tryNextEncoding() {
  910. if (currentEncodingIndex >= encodings.length) {
  911. showToast('无法正确读取文件,请确保文件编码正确');
  912. return;
  913. }
  914.  
  915. const currentEncoding = encodings[currentEncodingIndex];
  916. tryReadFile(currentEncoding)
  917. .then(text => {
  918. try {
  919. // 处理 BOM 标记
  920. const content = text.replace(/^\uFEFF/, '');
  921. const rows = content.split(/\r?\n/);
  922. const prompts = JSON.parse(localStorage.getItem('aiPrompts') || '{}');
  923. let importCount = 0;
  924.  
  925. // 跳过标题行
  926. for (let i = 1; i < rows.length; i++) {
  927. const row = rows[i].trim();
  928. if (!row) continue;
  929.  
  930. // 使用更可靠的CSV解析方式
  931. let columns = [];
  932. let currentColumn = '';
  933. let inQuotes = false;
  934.  
  935. // 手动解析CSV,处理引号内的逗号
  936. for (let j = 0; j < row.length; j++) {
  937. const char = row[j];
  938. if (char === '"') {
  939. inQuotes = !inQuotes;
  940. } else if (char === ',' && !inQuotes) {
  941. columns.push(currentColumn.trim());
  942. currentColumn = '';
  943. } else {
  944. currentColumn += char;
  945. }
  946. }
  947. columns.push(currentColumn.trim());
  948.  
  949. // 清理引号
  950. columns = columns.map(col => col.replace(/^["']|["']$/g, ''));
  951.  
  952. if (columns.length >= 2) {
  953. const [name, content, comment, category] = columns;
  954. if (name && content) {
  955. prompts[name] = {
  956. content: content,
  957. comment: comment || '',
  958. category: category || '其他'
  959. };
  960. importCount++;
  961. }
  962. }
  963. }
  964.  
  965. localStorage.setItem('aiPrompts', JSON.stringify(prompts));
  966. showToast(`成功导入 ${importCount} 个提示词`);
  967. loadPrompts();
  968. } catch (err) {
  969. console.error('解析失败:', err);
  970. showToast('文件格式错误,请检查CSV格式是否正确');
  971. }
  972. })
  973. .catch(err => {
  974. if (err === 'encoding_error') {
  975. currentEncodingIndex++;
  976. tryNextEncoding();
  977. } else {
  978. console.error('导入失败:', err);
  979. showToast('导入失败,请检查文件格式');
  980. }
  981. });
  982. }
  983.  
  984. tryNextEncoding();
  985. }
  986.  
  987. // 创建UI
  988. function createUI() {
  989. // 添加切换按钮
  990. const toggleButton = document.createElement('button');
  991. toggleButton.className = 'toggle-button';
  992. toggleButton.textContent = '提示词助手';
  993. toggleButton.onclick = function() {
  994. const helper = document.querySelector('.prompt-helper');
  995. helper.classList.toggle('collapsed');
  996. this.style.display = helper.classList.contains('collapsed') ? 'block' : 'none';
  997. };
  998. document.body.appendChild(toggleButton);
  999.  
  1000. const container = document.createElement('div');
  1001. container.className = 'prompt-helper';
  1002. container.innerHTML = `
  1003. <h3>
  1004. <span class="title">AI提示词助手</span>
  1005. <div class="buttons">
  1006. <button id="settingsBtn">设置</button>
  1007. <button id="toggleHelper">收起</button>
  1008. </div>
  1009. </h3>
  1010. <div class="settings-menu" id="settingsMenu">
  1011. <h4>显示设置</h4>
  1012. <div class="settings-item">
  1013. <label>
  1014. <input type="checkbox" id="showName" ${getDisplaySettings().showName ? 'checked' : ''}>
  1015. 显示名称
  1016. </label>
  1017. </div>
  1018. <div class="settings-item">
  1019. <label>
  1020. <input type="checkbox" id="showContent" ${getDisplaySettings().showContent ? 'checked' : ''}>
  1021. 显示内容
  1022. </label>
  1023. </div>
  1024. <div class="settings-item">
  1025. <label>
  1026. <input type="checkbox" id="showComment" ${getDisplaySettings().showComment ? 'checked' : ''}>
  1027. 显示注释
  1028. </label>
  1029. </div>
  1030. <div class="settings-item">
  1031. <label>
  1032. <input type="checkbox" id="showCategory" ${getDisplaySettings().showCategory ? 'checked' : ''}>
  1033. 显示分类
  1034. </label>
  1035. </div>
  1036. <div class="settings-item">
  1037. <label>
  1038. <input type="checkbox" id="isSimplified" ${getDisplaySettings().isSimplified ? 'checked' : ''}>
  1039. 简化显示(点击展开)
  1040. </label>
  1041. </div>
  1042. <div class="author-info">
  1043. <p>作者:初沐</p>
  1044. <p>联系方式:<a href="mailto:1501942742@qq.com">1501942742@qq.com</a></p>
  1045. <p>如有问题或建议,欢迎反馈</p>
  1046. </div>
  1047. </div>
  1048. <div class="import-toggle" id="importToggle">
  1049. <div class="arrow"></div>
  1050. <span class="import-toggle-text">导入提示词</span>
  1051. </div>
  1052. <div class="import-section collapsed" id="importSection">
  1053. <p>拖拽文件到此处或点击下方按钮导入</p>
  1054. <input type="file" id="fileInput" accept=".csv,.xlsx,.xls">
  1055. <button id="importBtn">导入提示词</button>
  1056. <button id="downloadTemplate">下载模板</button>
  1057. </div>
  1058. <input type="text" id="searchPrompt" class="search-box" placeholder="搜索提示词...">
  1059. <div class="controls">
  1060. <select id="categorySelect" style="flex: 1">
  1061. <option value="all">所有分类</option>
  1062. ${getCategories().map(category => `<option value="${category}">${category}</option>`).join('')}
  1063. </select>
  1064. <button id="addNewPrompt">新增提示词</button>
  1065. <button id="manageCategories">分类管理</button>
  1066. </div>
  1067. <div id="manageCategoriesForm" class="manage-categories">
  1068. <div style="display: flex; gap: 8px; margin-bottom: 10px;">
  1069. <input type="text" id="newCategory" placeholder="输入新分类名称" style="flex: 1">
  1070. <button id="addCategory">添加分类</button>
  1071. </div>
  1072. <div class="category-list" id="categoryList"></div>
  1073. </div>
  1074. <div id="addPromptForm" class="add-prompt-form">
  1075. <input type="text" id="promptName" placeholder="提示词名称" style="width: 100%">
  1076. <textarea id="promptContent" placeholder="提示词内容" style="width: 100%; height: 80px; margin: 8px 0"></textarea>
  1077. <textarea id="promptComment" placeholder="注释(可选)" style="width: 100%; height: 40px"></textarea>
  1078. <select id="promptCategory" style="width: 100%; margin: 8px 0">
  1079. <option value="洗稿">洗稿</option>
  1080. <option value="创作">创作</option>
  1081. <option value="翻译">翻译</option>
  1082. <option value="其他">其他</option>
  1083. </select>
  1084. <div style="text-align: right">
  1085. <button id="cancelPrompt">取消</button>
  1086. <button id="savePrompt">保存</button>
  1087. </div>
  1088. </div>
  1089. <div class="prompt-list" id="promptList"></div>
  1090. `;
  1091. document.body.appendChild(container);
  1092.  
  1093. // 初始化事件
  1094. initEvents();
  1095. loadPrompts();
  1096. updateCategoryList();
  1097. }
  1098.  
  1099. // 初始化
  1100. createUI();
  1101. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址