ParaTranz Tools

为 ParaTranz 添加正则表达式管理和机器翻译功能。

  1. // ==UserScript==
  2. // @name ParaTranz Tools
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.1
  5. // @description 为 ParaTranz 添加正则表达式管理和机器翻译功能。
  6. // @author HeliumOctahelide
  7. // @license WTFPL
  8. // @match https://paratranz.cn/projects/*/strings*
  9. // @icon https://paratranz.cn/favicon.png
  10. // @grant none
  11. // ==/UserScript==
  12.  
  13. (function() {
  14. 'use strict';
  15.  
  16. // 基类定义
  17. class BaseComponent {
  18. constructor(selector) {
  19. this.selector = selector;
  20. this.init();
  21. }
  22.  
  23. init() {
  24. this.checkExistence();
  25. }
  26.  
  27. checkExistence() {
  28. const element = document.querySelector(this.selector);
  29. if (!element) {
  30. this.insert();
  31. }
  32. setTimeout(() => this.checkExistence(), 1000);
  33. }
  34.  
  35. insert() {
  36. // 留空,子类实现具体插入逻辑
  37. }
  38. }
  39.  
  40. // 按钮类定义,继承自BaseComponent
  41. class Button extends BaseComponent {
  42. constructor(selector, toolbarSelector, htmlContent, callback) {
  43. super(selector);
  44. this.toolbarSelector = toolbarSelector;
  45. this.htmlContent = htmlContent;
  46. this.callback = callback;
  47. }
  48.  
  49. insert() {
  50. const toolbar = document.querySelector(this.toolbarSelector);
  51. if (!toolbar) {
  52. console.log(`Toolbar not found: ${this.toolbarSelector}`);
  53. return;
  54. }
  55. if (toolbar && !document.querySelector(this.selector)) {
  56. const button = document.createElement('button');
  57. button.className = this.selector.split('.').join(' ');
  58. button.innerHTML = this.htmlContent;
  59. button.type = 'button';
  60. button.addEventListener('click', this.callback);
  61. toolbar.insertAdjacentElement('afterbegin', button);
  62. console.log(`Button inserted: ${this.selector}`);
  63. }
  64. }
  65. }
  66.  
  67. // 手风琴类定义,继承自BaseComponent
  68. class Accordion extends BaseComponent {
  69. constructor(selector, parentSelector) {
  70. super(selector);
  71. this.parentSelector = parentSelector;
  72. }
  73.  
  74. insert() {
  75. const parentElement = document.querySelector(this.parentSelector);
  76. if (!parentElement) {
  77. console.log(`Parent element not found: ${this.parentSelector}`);
  78. return;
  79. }
  80. if (parentElement && !document.querySelector(this.selector)) {
  81. const accordionHTML = `
  82. <div class="accordion" id="accordionExample"></div>
  83. <hr>
  84. `;
  85. parentElement.insertAdjacentHTML('afterbegin', accordionHTML);
  86. }
  87. }
  88.  
  89. addCard(card) {
  90. card.insert();
  91. }
  92. }
  93.  
  94. // 卡片类定义,继承自BaseComponent
  95. class Card extends BaseComponent {
  96. constructor(selector, parentSelector, headingId, title, contentHTML) {
  97. super(selector);
  98. this.parentSelector = parentSelector;
  99. this.headingId = headingId;
  100. this.title = title;
  101. this.contentHTML = contentHTML;
  102. }
  103.  
  104. insert() {
  105. const parentElement = document.querySelector(this.parentSelector);
  106. if (!parentElement) {
  107. console.log(`Parent element not found: ${this.parentSelector}`);
  108. return;
  109. }
  110. if (parentElement && !document.querySelector(this.selector)) {
  111. const cardHTML = `
  112. <div class="card m-0">
  113. <div class="card-header p-0" id="${this.headingId}">
  114. <h2 class="mb-0">
  115. <button class="btn btn-link" type="button" aria-expanded="false" aria-controls="${this.selector.substring(1)}">
  116. ${this.title}
  117. </button>
  118. </h2>
  119. </div>
  120. <div id="${this.selector.substring(1)}" class="collapse" aria-labelledby="${this.headingId}" data-parent="#accordionExample" style="max-height: 70vh; overflow-y: auto;">
  121. <div class="card-body">
  122. ${this.contentHTML}
  123. </div>
  124. </div>
  125. </div>
  126. `;
  127. parentElement.insertAdjacentHTML('beforeend', cardHTML);
  128.  
  129. const toggleButton = document.querySelector(`#${this.headingId} button`);
  130. const collapseDiv = document.querySelector(this.selector);
  131. toggleButton.addEventListener('click', function() {
  132. if (collapseDiv.style.maxHeight === '0px' || !collapseDiv.style.maxHeight) {
  133. collapseDiv.style.display = 'block';
  134. requestAnimationFrame(() => {
  135. collapseDiv.style.maxHeight = collapseDiv.scrollHeight + 'px';
  136. });
  137. toggleButton.setAttribute('aria-expanded', 'true');
  138. } else {
  139. collapseDiv.style.maxHeight = '0px';
  140. toggleButton.setAttribute('aria-expanded', 'false');
  141. collapseDiv.addEventListener('transitionend', () => {
  142. if (collapseDiv.style.maxHeight === '0px') {
  143. collapseDiv.style.display = 'none';
  144. }
  145. }, { once: true });
  146. }
  147. });
  148.  
  149. collapseDiv.style.maxHeight = '0px';
  150. collapseDiv.style.overflow = 'hidden';
  151. collapseDiv.style.transition = 'max-height 0.3s ease';
  152. }
  153. }
  154. }
  155.  
  156. // 定义具体的正则管理卡片
  157. class RegexCard extends Card {
  158. constructor(parentSelector) {
  159. const headingId = 'headingOne';
  160. const contentHTML = `
  161. <div id="managePage">
  162. <div id="regexList"></div>
  163. <div class="regex-item mb-3 p-2" style="border: 1px solid #ccc; border-radius: 8px;">
  164. <input type="text" placeholder="Pattern" id="newPattern" class="form-control mb-2"/>
  165. <input type="text" placeholder="Replacement" id="newRepl" class="form-control mb-2"/>
  166. <button class="btn btn-secondary" id="addRegexButton">
  167. <i class="far fa-plus-circle"></i> 添加正则表达式
  168. </button>
  169. </div>
  170. <div class="mt-3">
  171. <button class="btn btn-primary" id="exportRegexButton">导出正则表达式</button>
  172. <input type="file" id="importRegexInput" class="d-none"/>
  173. <button class="btn btn-primary" id="importRegexButton">导入正则表达式</button>
  174. </div>
  175. </div>
  176. `;
  177. super('#collapseOne', parentSelector, headingId, '正则管理', contentHTML);
  178. }
  179.  
  180. insert() {
  181. super.insert();
  182. // 如果尚未插入则先略过
  183. if (!document.querySelector('#collapseOne')) {
  184. return;
  185. }
  186. document.getElementById('addRegexButton').addEventListener('click', this.addRegex);
  187. document.getElementById('exportRegexButton').addEventListener('click', this.exportRegex);
  188. document.getElementById('importRegexButton').addEventListener('click', () => {
  189. document.getElementById('importRegexInput').click();
  190. });
  191. document.getElementById('importRegexInput').addEventListener('change', this.importRegex);
  192. this.loadRegexList();
  193. }
  194.  
  195. addRegex = () => {
  196. const pattern = document.getElementById('newPattern').value;
  197. const repl = document.getElementById('newRepl').value;
  198.  
  199. if (pattern && repl) {
  200. // 获取当前存储的正则列表
  201. const regexList = JSON.parse(localStorage.getItem('regexList')) || [];
  202.  
  203. // 添加新的正则表达式
  204. regexList.push({ pattern, repl });
  205.  
  206. // 保存到 localStorage
  207. localStorage.setItem('regexList', JSON.stringify(regexList));
  208.  
  209. // 立即调用 loadRegexList 刷新页面
  210. this.loadRegexList();
  211.  
  212. // 清空输入框
  213. document.getElementById('newPattern').value = '';
  214. document.getElementById('newRepl').value = '';
  215. }
  216. };
  217.  
  218. loadRegexList() {
  219. const regexList = JSON.parse(localStorage.getItem('regexList')) || [];
  220. const regexListDiv = document.getElementById('regexList');
  221. regexListDiv.innerHTML = '';
  222. regexList.forEach((regex, index) => {
  223. const regexDiv = document.createElement('div');
  224. regexDiv.className = 'regex-item mb-3 p-2';
  225. regexDiv.style.border = '1px solid #ccc';
  226. regexDiv.style.borderRadius = '8px';
  227. regexDiv.style.transition = 'transform 0.3s';
  228. regexDiv.style.backgroundColor = regex.disabled ? '#f2dede' : '#fff';
  229. regexDiv.innerHTML = `
  230. <div class="mb-2">
  231. <input type="text" class="form-control mb-1" value="${regex.pattern}" data-index="${index}" data-type="pattern"/>
  232. <input type="text" class="form-control" value="${regex.repl}" data-index="${index}" data-type="repl"/>
  233. </div>
  234. <div class="d-flex justify-content-between">
  235. <div role="group" class="btn-group">
  236. <button class="btn btn-secondary moveUpButton" data-index="${index}" title="上移">
  237. <i class="fas fa-arrow-up"></i>
  238. </button>
  239. <button class="btn btn-secondary moveDownButton" data-index="${index}" title="下移">
  240. <i class="fas fa-arrow-down"></i>
  241. </button>
  242. <button class="btn btn-secondary toggleRegexButton" data-index="${index}" title="禁用/启用">
  243. <i class="${regex.disabled ? 'fas fa-toggle-off' : 'fas fa-toggle-on'}"></i>
  244. </button>
  245. <button class="btn btn-secondary matchRegexButton" data-index="${index}" title="匹配">
  246. <i class="fas fa-play"></i>
  247. </button>
  248. </div>
  249. <div role="group" class="btn-group">
  250. <button class="btn btn-success saveRegexButton" data-index="${index}" title="保存">
  251. <i class="far fa-save"></i>
  252. </button>
  253. <button class="btn btn-danger deleteRegexButton" data-index="${index}" title="删除">
  254. <i class="far fa-trash-alt"></i>
  255. </button>
  256. </div>
  257. </div>
  258. `;
  259. regexListDiv.appendChild(regexDiv);
  260. });
  261.  
  262. // 强制触发容器的重绘
  263. regexListDiv.style.display = 'none'; // 设置为不可见状态
  264. regexListDiv.offsetHeight; // 读取元素的高度,强制重绘
  265. regexListDiv.style.display = ''; // 重新设置为可见状态
  266.  
  267. document.querySelectorAll('.saveRegexButton').forEach(button => {
  268. button.addEventListener('click', () => {
  269. const index = button.getAttribute('data-index');
  270. this.saveRegex(index);
  271. });
  272. });
  273.  
  274. document.querySelectorAll('.deleteRegexButton').forEach(button => {
  275. button.addEventListener('click', () => {
  276. const index = button.getAttribute('data-index');
  277. this.deleteRegex(index);
  278. });
  279. });
  280.  
  281. document.querySelectorAll('.toggleRegexButton').forEach(button => {
  282. button.addEventListener('click', () => {
  283. const index = button.getAttribute('data-index');
  284. this.toggleRegex(index);
  285. });
  286. });
  287.  
  288. document.querySelectorAll('.matchRegexButton').forEach(button => {
  289. button.addEventListener('click', () => {
  290. const index = button.getAttribute('data-index');
  291. this.matchRegex(index);
  292. });
  293. });
  294.  
  295. document.querySelectorAll('.moveUpButton').forEach(button => {
  296. button.addEventListener('click', () => {
  297. const index = parseInt(button.getAttribute('data-index'));
  298. this.moveRegex(index, -1);
  299. });
  300. });
  301.  
  302. document.querySelectorAll('.moveDownButton').forEach(button => {
  303. button.addEventListener('click', () => {
  304. const index = parseInt(button.getAttribute('data-index'));
  305. this.moveRegex(index, 1);
  306. });
  307. });
  308. }
  309.  
  310. saveRegex() {
  311. const regexItems = document.querySelectorAll('.regex-item');
  312. const updatedRegexList = [];
  313.  
  314. regexItems.forEach(item => {
  315. const patternInput = item.querySelector('input[data-type="pattern"]');
  316. const replInput = item.querySelector('input[data-type="repl"]');
  317. const disabled = item.style.backgroundColor === '#f2dede';
  318.  
  319. if (patternInput && replInput) {
  320. updatedRegexList.push({
  321. pattern: patternInput.value,
  322. repl: replInput.value,
  323. disabled: disabled
  324. });
  325. }
  326. });
  327.  
  328. localStorage.setItem('regexList', JSON.stringify(updatedRegexList));
  329. this.loadRegexList();
  330. }
  331.  
  332. deleteRegex(index) {
  333. const regexList = JSON.parse(localStorage.getItem('regexList')) || [];
  334. regexList.splice(index, 1);
  335. localStorage.setItem('regexList', JSON.stringify(regexList));
  336. this.loadRegexList();
  337. }
  338.  
  339. toggleRegex(index) {
  340. const regexList = JSON.parse(localStorage.getItem('regexList')) || [];
  341. regexList[index].disabled = !regexList[index].disabled;
  342. localStorage.setItem('regexList', JSON.stringify(regexList));
  343. this.loadRegexList();
  344. }
  345.  
  346. matchRegex(index) {
  347. const regexList = JSON.parse(localStorage.getItem('regexList')) || [];
  348. const regex = regexList[index];
  349. const textareas = document.querySelectorAll('textarea.translation.form-control');
  350.  
  351. textareas.forEach(textarea => {
  352. let text = textarea.value;
  353. const pattern = new RegExp(regex.pattern, 'g');
  354. text = text.replace(pattern, regex.repl);
  355. this.simulateInputChange(textarea, text);
  356. });
  357. }
  358.  
  359. moveRegex(index, direction) {
  360. const regexList = JSON.parse(localStorage.getItem('regexList')) || [];
  361. const newIndex = index + direction;
  362. if (newIndex >= 0 && newIndex < regexList.length) {
  363. const [movedItem] = regexList.splice(index, 1);
  364. regexList.splice(newIndex, 0, movedItem);
  365. localStorage.setItem('regexList', JSON.stringify(regexList));
  366. this.loadRegexListWithAnimation(index, newIndex);
  367. }
  368. }
  369.  
  370. loadRegexListWithAnimation(oldIndex, newIndex) {
  371. const regexListDiv = document.getElementById('regexList');
  372. const items = regexListDiv.querySelectorAll('.regex-item');
  373. const oldItem = items[oldIndex];
  374. const newItem = items[newIndex];
  375.  
  376. oldItem.style.transform = `translateY(${(newIndex - oldIndex) * 100}%)`;
  377. newItem.style.transform = `translateY(${(oldIndex - newIndex) * 100}%)`;
  378.  
  379. setTimeout(() => {
  380. this.loadRegexList();
  381. }, 300);
  382. }
  383.  
  384. simulateInputChange(element, newValue) {
  385. const inputEvent = new Event('input', { bubbles: true });
  386. const originalValue = element.value;
  387. element.value = newValue;
  388.  
  389. const tracker = element._valueTracker;
  390. if (tracker) {
  391. tracker.setValue(originalValue);
  392. }
  393.  
  394. element.dispatchEvent(inputEvent);
  395. }
  396.  
  397. exportRegex() {
  398. const regexList = JSON.parse(localStorage.getItem('regexList')) || [];
  399. const json = JSON.stringify(regexList, null, 2);
  400. const blob = new Blob([json], { type: 'application/json' });
  401. const url = URL.createObjectURL(blob);
  402. const a = document.createElement('a');
  403. a.href = url;
  404. a.download = 'regexList.json';
  405. a.click();
  406. URL.revokeObjectURL(url);
  407. }
  408.  
  409. importRegex(event) {
  410. const file = event.target.files[0];
  411. const reader = new FileReader();
  412. reader.onload = event => {
  413. const content = event.target.result;
  414. const regexList = JSON.parse(content);
  415. localStorage.setItem('regexList', JSON.stringify(regexList));
  416. this.loadRegexList();
  417. };
  418. reader.readAsText(file);
  419. }
  420. }
  421.  
  422. // 定义具体的机器翻译卡片
  423. class MachineTranslationCard extends Card {
  424. constructor(parentSelector) {
  425. const headingId = 'headingTwo';
  426. const contentHTML = `
  427. <button class="btn btn-primary" id="openTranslationConfigButton">配置翻译</button>
  428. <div class="mt-3">
  429. <div class="d-flex">
  430. <textarea id="originalText" class="form-control" style="width: 100%; height: 25vh;"></textarea>
  431. <div class="d-flex flex-column ml-2">
  432. <button class="btn btn-secondary mb-2" id="copyOriginalButton">
  433. <i class="fas fa-copy"></i>
  434. </button>
  435. <button class="btn btn-secondary" id="translateButton">
  436. <i class="fas fa-globe"></i>
  437. </button>
  438. </div>
  439. </div>
  440. <div class="d-flex mt-2">
  441. <textarea id="translatedText" class="form-control" style="width: 100%; height: 25vh;"></textarea>
  442. <div class="d-flex flex-column ml-2">
  443. <button class="btn btn-secondary mb-2" id="pasteTranslationButton">
  444. <i class="fas fa-arrow-alt-left"></i>
  445. </button>
  446. <button class="btn btn-secondary" id="copyTranslationButton">
  447. <i class="fas fa-copy"></i>
  448. </button>
  449. </div>
  450. </div>
  451. </div>
  452.  
  453. <!-- Translation Configuration Modal -->
  454. <div class="modal" id="translationConfigModal" tabindex="-1" role="dialog" style="display: none;">
  455. <div class="modal-dialog" role="document">
  456. <div class="modal-content">
  457. <div class="modal-header">
  458. <h5 class="modal-title">翻译配置</h5>
  459. <button type="button" class="close" id="closeTranslationConfigModal" aria-label="Close">
  460. <span aria-hidden="true">&times;</span>
  461. </button>
  462. </div>
  463. <div class="modal-body">
  464. <form id="translationConfigForm">
  465. <div class="form-group">
  466. <label for="baseUrl">Base URL</label>
  467. <input type="text" class="form-control" id="baseUrl" placeholder="Enter base URL">
  468. </div>
  469. <div class="form-group">
  470. <label for="apiKey">API Key</label>
  471. <input type="text" class="form-control" id="apiKey" placeholder="Enter API key">
  472. </div>
  473. <div class="form-group">
  474. <label for="model">Model</label>
  475. <input type="text" class="form-control" id="model" placeholder="Enter model">
  476. </div>
  477. <div class="form-group">
  478. <label for="temperature">Prompt</label>
  479. <input type="text" class="form-control" id="prompt" placeholder="Enter prompt or use default prompt">
  480. </div>
  481. <div class="form-group">
  482. <label for="temperature">Temperature</label>
  483. <input type="number" step="0.1" class="form-control" id="temperature" placeholder="Enter temperature">
  484. </div>
  485. </form>
  486. </div>
  487. <div class="modal-footer">
  488. <button type="button" class="btn btn-secondary" id="closeTranslationConfigModalButton">关闭</button>
  489. </div>
  490. </div>
  491. </div>
  492. </div>
  493. `;
  494. super('#collapseTwo', parentSelector, headingId, '机器翻译', contentHTML);
  495. }
  496.  
  497. insert() {
  498. super.insert();
  499. if (!document.querySelector('#collapseTwo')) {
  500. return;
  501. }
  502. const translationConfigModal = document.getElementById('translationConfigModal');
  503. document.getElementById('openTranslationConfigButton').addEventListener('click', function() {
  504. translationConfigModal.style.display = 'block';
  505. });
  506.  
  507. function closeModal() {
  508. translationConfigModal.style.display = 'none';
  509. }
  510.  
  511. document.getElementById('closeTranslationConfigModal').addEventListener('click', closeModal);
  512. document.getElementById('closeTranslationConfigModalButton').addEventListener('click', closeModal);
  513.  
  514. const baseUrlInput = document.getElementById('baseUrl');
  515. const apiKeyInput = document.getElementById('apiKey');
  516. const modelSelect = document.getElementById('model');
  517. const promptInput = document.getElementById('prompt');
  518. const temperatureInput = document.getElementById('temperature');
  519.  
  520. baseUrlInput.value = localStorage.getItem('baseUrl') || '';
  521. apiKeyInput.value = localStorage.getItem('apiKey') || '';
  522. modelSelect.value = localStorage.getItem('model') || 'gpt-4o-mini';
  523. promptInput.value = localStorage.getItem('prompt') || '';
  524. temperatureInput.value = localStorage.getItem('temperature') || '';
  525.  
  526. baseUrlInput.addEventListener('input', function() {
  527. localStorage.setItem('baseUrl', baseUrlInput.value);
  528. });
  529.  
  530. apiKeyInput.addEventListener('input', function() {
  531. localStorage.setItem('apiKey', apiKeyInput.value);
  532. });
  533.  
  534. modelSelect.addEventListener('input', function() {
  535. localStorage.setItem('model', modelSelect.value);
  536. });
  537.  
  538. promptInput.addEventListener('input', function() {
  539. localStorage.setItem('prompt', promptInput.value);
  540. });
  541.  
  542. temperatureInput.addEventListener('input', function() {
  543. localStorage.setItem('temperature', temperatureInput.value);
  544. });
  545.  
  546. this.setupTranslation();
  547. }
  548.  
  549. setupTranslation() {
  550. // 更新Original Text
  551. function updateOriginalText() {
  552. const originalDiv = document.querySelector('.original.well');
  553. if (originalDiv) {
  554. const originalText = originalDiv.innerText;
  555. document.getElementById('originalText').value = originalText;
  556. }
  557. }
  558.  
  559. // 监控Original Text变化
  560. const observer = new MutationObserver(updateOriginalText);
  561. const config = { childList: true, subtree: true };
  562. const originalDiv = document.querySelector('.original.well');
  563. if (originalDiv) {
  564. observer.observe(originalDiv, config);
  565. }
  566.  
  567. document.getElementById('copyOriginalButton').addEventListener('click', updateOriginalText);
  568.  
  569. // 翻译功能
  570. document.getElementById('translateButton').addEventListener('click', async function() {
  571. const originalText = document.getElementById('originalText').value;
  572. console.log('Translating:', originalText);
  573.  
  574. const model = localStorage.getItem('model') || 'gpt-4o-mini';
  575. const prompt = localStorage.getItem('prompt') || 'You are a professional translator focusing on translating Magic: The Gathering cards from English to Chinese. You are given a card\'s original text in English. Translate it into Chinese.';
  576. const temperature = parseFloat(localStorage.getItem('temperature')) || 0;
  577.  
  578. document.getElementById('translatedText').value = '翻译中...';
  579. let translatedText = await translateText(originalText, model, prompt, temperature);
  580. // 正则替换
  581. const regexList = JSON.parse(localStorage.getItem('regexList')) || [];
  582. regexList.forEach(regex => {
  583. if (!regex.disabled) {
  584. const pattern = new RegExp(regex.pattern, 'g');
  585. translatedText = translatedText.replace(pattern, regex.repl);
  586. }
  587. });
  588. document.getElementById('translatedText').value = translatedText;
  589. });
  590.  
  591. // 复制译文到剪切板
  592. document.getElementById('copyTranslationButton').addEventListener('click', function() {
  593. const translatedText = document.getElementById('translatedText').value;
  594. navigator.clipboard.writeText(translatedText).then(() => {
  595. console.log('Translated text copied to clipboard');
  596. }).catch(err => {
  597. console.error('Failed to copy text: ', err);
  598. });
  599. });
  600.  
  601. // 粘贴译文
  602. document.getElementById('pasteTranslationButton').addEventListener('click', function() {
  603. const translatedText = document.getElementById('translatedText').value;
  604. simulateInputChange(document.querySelector('textarea.translation.form-control'), translatedText);
  605. });
  606. }
  607. }
  608.  
  609. // 翻译函数定义
  610. async function translateText(query, model, prompt, temperature) {
  611. const API_SECRET_KEY = localStorage.getItem('apiKey');
  612. const BASE_URL = localStorage.getItem('baseUrl');
  613. if (!prompt) {
  614. prompt = "You are a professional translator focusing on translating Magic: The Gathering cards from English to Chinese. You are given a card's original text in English. Translate it into Chinese.";
  615. }
  616.  
  617. const requestBody = {
  618. model: model,
  619. temperature: temperature,
  620. messages: [
  621. { role: "system", content: prompt },
  622. { role: "user", content: query }
  623. ]
  624. };
  625.  
  626. try {
  627. const response = await fetch(`${BASE_URL}chat/completions`, {
  628. method: 'POST',
  629. headers: {
  630. 'Content-Type': 'application/json',
  631. 'Authorization': `Bearer ${API_SECRET_KEY}`
  632. },
  633. body: JSON.stringify(requestBody)
  634. });
  635. const data = await response.json();
  636. return data.choices[0].message.content;
  637. } catch (error) {
  638. console.error('Error:', error);
  639. return "翻译失败,请检查配置和网络连接。";
  640. }
  641. }
  642.  
  643. function simulateInputChange(element, newValue) {
  644. const inputEvent = new Event('input', { bubbles: true });
  645. const originalValue = element.value;
  646. element.value = newValue;
  647.  
  648. const tracker = element._valueTracker;
  649. if (tracker) {
  650. tracker.setValue(originalValue);
  651. }
  652.  
  653. element.dispatchEvent(inputEvent);
  654. }
  655.  
  656. // 初始化组件
  657. const accordion = new Accordion('#accordionExample', '.sidebar-right');
  658. const regexCard = new RegexCard('#accordionExample');
  659. const machineTranslationCard = new MachineTranslationCard('#accordionExample');
  660.  
  661. accordion.addCard(regexCard);
  662. accordion.addCard(machineTranslationCard);
  663.  
  664. const runButton = new Button('.btn.btn-secondary.match-button', '.toolbar .right .btn-group', '<i class="fas fa-play"></i> 匹配', function() {
  665. const regexList = JSON.parse(localStorage.getItem('regexList')) || [];
  666. const textareas = document.querySelectorAll('textarea.translation.form-control');
  667.  
  668. textareas.forEach(textarea => {
  669. let text = textarea.value;
  670. regexList.forEach(regex => {
  671. if (!regex.disabled) {
  672. const pattern = new RegExp(regex.pattern, 'g');
  673. text = text.replace(pattern, regex.repl);
  674. }
  675. });
  676. simulateInputChange(textarea, text);
  677. });
  678. });
  679. })();