// ==UserScript== // @name ChatGPT 語音輸入介面 (支援中/英/日/韓語言) // @version 1.0 // @description 讓你可以透過語音輸入要問 ChatGPT 的問題 (支援中文、英文、日文、韓文) // @license MIT // @homepage https://wayneblog.ga/ // @homepageURL https://wayneblog.ga/ // @website https://www.facebook.com/wayne.blog.ga // @source https://github.com/wjdesign/TampermonkeyScripts/raw/main/ChatGPTWithVoiceInput.user.js // @namespace https://github.com/wjdesign/TampermonkeyScripts/raw/main/ChatGPTWithVoiceInput.user.js // @match *://chat.openai.com/chat // @author Wayne // @run-at document-idle // @downloadURL https://update.greasyfork.icu/scripts/456597/ChatGPT%20%E8%AA%9E%E9%9F%B3%E8%BC%B8%E5%85%A5%E4%BB%8B%E9%9D%A2%20%28%E6%94%AF%E6%8F%B4%E4%B8%AD%E8%8B%B1%E6%97%A5%E9%9F%93%E8%AA%9E%E8%A8%80%29.user.js // @updateURL https://update.greasyfork.icu/scripts/456597/ChatGPT%20%E8%AA%9E%E9%9F%B3%E8%BC%B8%E5%85%A5%E4%BB%8B%E9%9D%A2%20%28%E6%94%AF%E6%8F%B4%E4%B8%AD%E8%8B%B1%E6%97%A5%E9%9F%93%E8%AA%9E%E8%A8%80%29.meta.js // ==/UserScript== (function () { 'use strict'; let sti = setInterval(() => { if (document.activeElement.tagName === 'TEXTAREA' && document.activeElement.nextSibling.tagName === 'BUTTON') { var vi = new VoiceInputHelper(document.activeElement, document.activeElement.nextSibling); vi.Start(); document.addEventListener('keydown', (ev) => { if (ev.altKey && (ev.key === 'S' || ev.key === 'T') && !/^(?:input|select|textarea|button)$/i.test(ev.target.nodeName)) { alert('你是不是不小心按到了 CAPSLOCK 鍵?'); return; } if (ev.altKey && ev.key === 's' /* && !/^(?:input|select|textarea|button)$/i.test(ev.target.nodeName) */) { if (!vi.IsStarted) { vi.Restart = true; vi.Start(); } } if (ev.altKey && ev.key === 't' /* && !/^(?:input|select|textarea|button)$/i.test(ev.target.nodeName) */) { if (vi.IsStarted) { vi.Restart = false; vi.Stop(); } } }); clearInterval(sti); } }, 100); class VoiceInputHelper { IsStarted = false; parts = []; Restart = true; Lang = 'cmn-Hant-TW'; constructor(textarea, button, lang) { // console.log(textarea, button); // https://developer.mozilla.org/en-US/docs/Web/API/Web_Speech_API/Using_the_Web_Speech_API const SpeechRecognition = window.SpeechRecognition || webkitSpeechRecognition; // const SpeechGrammarList = window.SpeechGrammarList || webkitSpeechGrammarList; // const SpeechRecognitionEvent = window.SpeechRecognitionEvent || webkitSpeechRecognitionEvent; this.recognition = new SpeechRecognition(); // show.value = "您好,接下來的對話請都使用繁體中文回應。"; // button.click(); this.recognition.continuous = false; this.recognition.interimResults = true; this.setLang(); this.recognition.onstart = () => { console.log('開始進行 SpeechRecognition 語音辨識'); this.IsStarted = true; }; this.recognition.onend = () => { console.log('停止 SpeechRecognition 語音辨識!'); this.IsStarted = false; setTimeout(() => { if (this.Restart) { this.recognition.start(); } }, 60); }; this.recognition.onresult = (event) => { // console.log('語音事件: ', event); var i = event.resultIndex; let results = event.results[i]; console.log('results.length', results.length); let transcript = results[0].transcript; // 理論上只會有一個結果 console.log('語音輸入: ' + transcript, 'isFinal: ', results.isFinal); if (this.parts.length == 0) { this.parts[0] = transcript; } else { this.parts[this.parts.length - 1] = transcript; } textarea.value = this.parts.join('') + '...'; textarea.dispatchEvent(new Event('input', {bubbles:true})); if (results.isFinal) { console.log('Final Result: ', results); switch (this.parts[this.parts.length - 1]) { case '送出': case 'Submit': case 'submit': case '跑起來': case '去吧': case 'enter': case 'Enter': case 'Run': case 'run': case 'Go': case 'go': this.parts.pop(); if (this.parts.length > 0) { textarea.value = this.parts.join(''); textarea.dispatchEvent(new Event('input', {bubbles:true})); button.click(); this.parts = []; } break; case '清空': case '淨空': case 'clear': this.parts = []; break; case '刪除': case '刪除上一句': this.parts.pop(); this.parts.pop(); break; case '逗號': case '逗點': case '都好': this.parts[this.parts.length - 1] = ','; break; case '句號': case '句點': this.parts[this.parts.length - 1] = '。'; break; case '問號': this.parts[this.parts.length - 1] = '?'; break; case '斷行': this.parts[this.parts.length - 1] = '\r\n'; break; case '重置': case 'リセット': // Risetto case '초기화': // chogihwa case 'reset': this.setLang('cmn-Hant-TW'); this.parts = []; break; case '切換至中文模式': case '切換至中文': case 'switch to Chinese mode': this.setLang('cmn-Hant-TW'); this.parts[this.parts.length - 1] = ''; break; case '切換至英文模式': case '切換至英文': console.log('切換至英文模式'); this.setLang('en-US'); this.parts[this.parts.length - 1] = ''; break; case '切換至日文模式': case '切換至日文': console.log('切換至日文模式'); this.setLang('ja-JP'); this.parts[this.parts.length - 1] = ''; break; case '切換至韓文模式': case '切換至韓文': console.log('切換至韓文模式'); this.setLang('ko-KR'); this.parts[this.parts.length - 1] = ''; break; case '關閉語音辨識': case '關閉語音': this.Stop(); break; default: this.parts[this.parts.length - 1] = this.parts[this.parts.length - 1].replace(/\.\.\.$/g, ''); if (this.parts[this.parts.length - 1].split('').pop() === '嗎') { this.parts[this.parts.length - 1] += '?'; } break; } this.parts = [...this.parts, '']; textarea.value = this.parts.join(''); textarea.dispatchEvent(new Event('input', {bubbles:true})); } }; } setLang(lang) { // https://stackoverflow.com/a/68742566/910074 if (lang) { this.Lang = lang; } this.recognition.lang = this.Lang; } Start() { this.recognition.start(); } Stop() { this.restart = false; this.recognition.stop(); } } })();