diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e20fde9..f65a0d8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -194,13 +194,13 @@ jobs: - name: Query version number run: echo "version=${GITHUB_REF:11}" >> $GITHUB_ENV - publish-winget: - # Action can only be run on windows - runs-on: windows-latest - needs: [build-chatgpt, release-chatgpt] - steps: - - uses: vedantmgoyal2009/winget-releaser@v1 - with: - identifier: lencx.ChatGPT - token: ${{ secrets.WINGET_TOKEN }} - version: ${{ github.event.release.tag_name }} + # publish-winget: + # # Action can only be run on windows + # runs-on: windows-latest + # needs: [build-chatgpt, release-chatgpt] + # steps: + # - uses: vedantmgoyal2009/winget-releaser@v1 + # with: + # identifier: lencx.ChatGPT + # token: ${{ secrets.WINGET_TOKEN }} + # version: ${{ github.event.release.tag_name }} diff --git a/README-ZH_CN.md b/README-ZH_CN.md index c7794cb..ffa20f2 100644 --- a/README-ZH_CN.md +++ b/README-ZH_CN.md @@ -32,7 +32,7 @@ ### Windows -- [ChatGPT_0.11.1_windows_x86_64.msi](https://github.com/lencx/ChatGPT/releases/download/v0.11.1/ChatGPT_0.11.1_windows_x86_64.msi) +- [ChatGPT_0.12.0_windows_x86_64.msi](https://github.com/lencx/ChatGPT/releases/download/v0.12.0/ChatGPT_0.12.0_windows_x86_64.msi) - 使用 [winget](https://winstall.app/apps/lencx.ChatGPT): ```bash @@ -43,12 +43,12 @@ winget install --id=lencx.ChatGPT -e --version 0.10.0 ``` -**注意:如果安装路径和应用名称相同,会导致冲突 ([#142](https://github.com/lencx/ChatGPT/issues/142#issuecomment-0.11.1))** +**注意:如果安装路径和应用名称相同,会导致冲突 ([#142](https://github.com/lencx/ChatGPT/issues/142#issuecomment-0.12.0))** ### Mac -- [ChatGPT_0.11.1_macos_aarch64.dmg](https://github.com/lencx/ChatGPT/releases/download/v0.11.1/ChatGPT_0.11.1_macos_aarch64.dmg) -- [ChatGPT_0.11.1_macos_x86_64.dmg](https://github.com/lencx/ChatGPT/releases/download/v0.11.1/ChatGPT_0.11.1_macos_x86_64.dmg) +- [ChatGPT_0.12.0_macos_aarch64.dmg](https://github.com/lencx/ChatGPT/releases/download/v0.12.0/ChatGPT_0.12.0_macos_aarch64.dmg) +- [ChatGPT_0.12.0_macos_x86_64.dmg](https://github.com/lencx/ChatGPT/releases/download/v0.12.0/ChatGPT_0.12.0_macos_x86_64.dmg) - Homebrew \ _[Homebrew 快捷安装](https://brew.sh) ([Cask](https://docs.brew.sh/Cask-Cookbook)):_ ```sh @@ -70,8 +70,8 @@ sudo xattr -r -d com.apple.quarantine /YOUR_PATH/ChatGPT.app ### Linux -- [ChatGPT_0.11.1_linux_x86_64.deb](https://github.com/lencx/ChatGPT/releases/download/v0.11.1/ChatGPT_0.11.1_linux_x86_64.deb) -- [ChatGPT_0.11.1_linux_x86_64.AppImage.tar.gz](https://github.com/lencx/ChatGPT/releases/download/v0.11.1/ChatGPT_0.11.1_linux_x86_64.AppImage.tar.gz): **工作可靠,`.deb` 运行失败时可以尝试它** +- [ChatGPT_0.12.0_linux_x86_64.deb](https://github.com/lencx/ChatGPT/releases/download/v0.12.0/ChatGPT_0.12.0_linux_x86_64.deb) +- [ChatGPT_0.12.0_linux_x86_64.AppImage.tar.gz](https://github.com/lencx/ChatGPT/releases/download/v0.12.0/ChatGPT_0.12.0_linux_x86_64.AppImage.tar.gz): **工作可靠,`.deb` 运行失败时可以尝试它** diff --git a/README.md b/README.md index 2c3cc42..1164ea7 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ ### Windows -- [ChatGPT_0.11.1_windows_x86_64.msi](https://github.com/lencx/ChatGPT/releases/download/v0.11.1/ChatGPT_0.11.1_windows_x86_64.msi): Direct download installer +- [ChatGPT_0.12.0_windows_x86_64.msi](https://github.com/lencx/ChatGPT/releases/download/v0.12.0/ChatGPT_0.12.0_windows_x86_64.msi): Direct download installer - Use [winget](https://winstall.app/apps/lencx.ChatGPT): ```bash @@ -52,12 +52,12 @@ winget install --id=lencx.ChatGPT -e --version 0.10.0 ``` -**Note: If the installation path and application name are the same, it will lead to conflict ([#142](https://github.com/lencx/ChatGPT/issues/142#issuecomment-0.11.1))** +**Note: If the installation path and application name are the same, it will lead to conflict ([#142](https://github.com/lencx/ChatGPT/issues/142#issuecomment-0.12.0))** ### Mac -- [ChatGPT_0.11.1_macos_aarch64.dmg](https://github.com/lencx/ChatGPT/releases/download/v0.11.1/ChatGPT_0.11.1_macos_aarch64.dmg): Direct download installer -- [ChatGPT_0.11.1_macos_x86_64.dmg](https://github.com/lencx/ChatGPT/releases/download/v0.11.1/ChatGPT_0.11.1_macos_x86_64.dmg): Direct download installer +- [ChatGPT_0.12.0_macos_aarch64.dmg](https://github.com/lencx/ChatGPT/releases/download/v0.12.0/ChatGPT_0.12.0_macos_aarch64.dmg): Direct download installer +- [ChatGPT_0.12.0_macos_x86_64.dmg](https://github.com/lencx/ChatGPT/releases/download/v0.12.0/ChatGPT_0.12.0_macos_x86_64.dmg): Direct download installer - Homebrew \ Or you can install with _[Homebrew](https://brew.sh) ([Cask](https://docs.brew.sh/Cask-Cookbook)):_ ```sh @@ -79,8 +79,8 @@ sudo xattr -r -d com.apple.quarantine /YOUR_PATH/ChatGPT.app ### Linux -- [ChatGPT_0.11.1_linux_x86_64.deb](https://github.com/lencx/ChatGPT/releases/download/v0.11.1/ChatGPT_0.11.1_linux_x86_64.deb): Download `.deb` installer, advantage small size, disadvantage poor compatibility -- [ChatGPT_0.11.1_linux_x86_64.AppImage.tar.gz](https://github.com/lencx/ChatGPT/releases/download/v0.11.1/ChatGPT_0.11.1_linux_x86_64.AppImage.tar.gz): Works reliably, you can try it if `.deb` fails to run +- [ChatGPT_0.12.0_linux_x86_64.deb](https://github.com/lencx/ChatGPT/releases/download/v0.12.0/ChatGPT_0.12.0_linux_x86_64.deb): Download `.deb` installer, advantage small size, disadvantage poor compatibility +- [ChatGPT_0.12.0_linux_x86_64.AppImage.tar.gz](https://github.com/lencx/ChatGPT/releases/download/v0.12.0/ChatGPT_0.12.0_linux_x86_64.AppImage.tar.gz): Works reliably, you can try it if `.deb` fails to run @@ -109,6 +109,7 @@ You can look at **[awesome-chatgpt-prompts](https://github.com/f/awesome-chatgpt ## ✨ Features - Multi-platform: `macOS` `Linux` `Windows` +- Text-to-Speech - Export ChatGPT history (PNG, PDF and Markdown) - The main window and system tray support custom URLs to wrap any website into a desktop application - Automatic application upgrade notification diff --git a/UPDATE_LOG.md b/UPDATE_LOG.md index de80a22..008573c 100644 --- a/UPDATE_LOG.md +++ b/UPDATE_LOG.md @@ -13,6 +13,14 @@ **New repository: https://github.com/lencx/nofwl** +## v0.12.0 + +Feat: + +- Add refresh button +- Add text-to-speech (`Control Center -> Settings -> General -> Set Speech Language`) (https://github.com/lencx/ChatGPT/issues/534) +- Automatically focus input field (https://github.com/lencx/ChatGPT/issues/550) + ## v0.11.1 Fix: diff --git a/casks/chatgpt.rb b/casks/chatgpt.rb index 4d1d633..1a353c1 100644 --- a/casks/chatgpt.rb +++ b/casks/chatgpt.rb @@ -1,9 +1,9 @@ cask "chatgpt" do - version "0.11.0" + version "0.12.0" arch = Hardware::CPU.arch.to_s sha256s = { - "x86_64" => "5f8013bee34daa53be8612b751955f745e7af9ef85b3541eba304b45176b6d8a", - "aarch64" => "a5d914277d16827c5e3c641abd80c7978f78b8ccf36bf08661e1bc06efc6224e" + "x86_64" => "d7f32d11f86ad8ac073dd451452124324e1c9154c318f15b77b5cd254000a3c4", + "aarch64" => "c4c10eeb4a2c9a885da13047340372f461d411711c20472fc673fbf958bf6378" } if arch == "arm64" then arch = "aarch64" end url "https://github.com/lencx/ChatGPT/releases/download/v#{version}/ChatGPT_#{version}_macos_#{arch}.dmg" diff --git a/src-tauri/src/app/setup.rs b/src-tauri/src/app/setup.rs index 3a701f1..54ff880 100644 --- a/src-tauri/src/app/setup.rs +++ b/src-tauri/src/app/setup.rs @@ -88,6 +88,7 @@ pub fn init(app: &mut App) -> std::result::Result<(), Box .initialization_script(include_str!("../scripts/export.js")) .initialization_script(include_str!("../scripts/markdown.export.js")) .initialization_script(include_str!("../scripts/cmd.js")) + .initialization_script(include_str!("../scripts/chat.js")) } main_win.build().unwrap(); diff --git a/src-tauri/src/app/window.rs b/src-tauri/src/app/window.rs index 1e56da9..60448fb 100644 --- a/src-tauri/src/app/window.rs +++ b/src-tauri/src/app/window.rs @@ -31,6 +31,7 @@ pub fn tray_window(handle: &tauri::AppHandle) { .initialization_script(include_str!("../vendors/floating-ui-core.js")) .initialization_script(include_str!("../vendors/floating-ui-dom.js")) .initialization_script(include_str!("../scripts/cmd.js")) + .initialization_script(include_str!("../scripts/chat.js")) .initialization_script(include_str!("../scripts/popup.core.js")) } diff --git a/src-tauri/src/conf.rs b/src-tauri/src/conf.rs index 2faef14..ebaff9c 100644 --- a/src-tauri/src/conf.rs +++ b/src-tauri/src/conf.rs @@ -40,6 +40,7 @@ pub_struct!(AppConf { save_window_state: bool, global_shortcut: Option, default_origin: String, + speech_lang: String, // Main Window isinit: bool, @@ -69,6 +70,10 @@ impl AppConf { save_window_state: false, theme: "light".into(), auto_update: "prompt".into(), + #[cfg(target_os = "macos")] + speech_lang: "com.apple.eloquence.en-US.Rocko".into(), + #[cfg(not(target_os = "macos"))] + speech_lang: "".into(), tray: true, popup_search: false, isinit: true, diff --git a/src-tauri/src/scripts/chat.js b/src-tauri/src/scripts/chat.js new file mode 100644 index 0000000..37c3bc2 --- /dev/null +++ b/src-tauri/src/scripts/chat.js @@ -0,0 +1,124 @@ +async function init() { + new MutationObserver(function (mutationsList) { + for (const mutation of mutationsList) { + if (mutation.target.closest("form")) { + chatBtns(); + } + } + }).observe(document.body, { + childList: true, + subtree: true, + }); + document.addEventListener('visibilitychange', () => + document.getElementsByTagName('textarea')[0]?.focus() + ); +} + +async function chatBtns() { + const chatConf = await invoke('get_app_conf') || {}; + const synth = window.speechSynthesis; + let currentUtterance = null; + let currentIndex = -1; + const list = Array.from(document.querySelectorAll("main >div>div>div>div>div")); + list.forEach((i, idx) => { + if (i.querySelector('.chat-item-copy')) return; + if (!i.querySelector('button.rounded-md')) return; + if (!i.querySelector('.self-end')) return; + const cpbtn = i.querySelector('button.rounded-md').cloneNode(true); + cpbtn.classList.add('chat-item-copy'); + cpbtn.title = 'Copy to clipboard'; + cpbtn.innerHTML = setIcon('copy'); + i.querySelector('.self-end').appendChild(cpbtn); + cpbtn.onclick = () => { + copyToClipboard(i?.innerText?.trim() || '', cpbtn); + } + + const saybtn = i.querySelector('button.rounded-md').cloneNode(true); + saybtn.classList.add('chat-item-voice'); + saybtn.title = 'Say'; + saybtn.innerHTML = setIcon('voice'); + i.querySelector('.self-end').appendChild(saybtn); + saybtn.onclick = () => { + if (currentUtterance && currentIndex !== -1) { + synth.cancel(); + if (idx === currentIndex) { + saybtn.innerHTML = setIcon('voice'); + currentUtterance = null; + currentIndex = -1; + return; + } else if (list[currentIndex].querySelector('.chat-item-voice')) { + list[currentIndex].querySelector('.chat-item-voice').innerHTML = setIcon('voice'); + list[idx].querySelector('.chat-item-voice').innerHTML = setIcon('speaking'); + } + } + const txt = i?.innerText?.trim() || ''; + if (!txt) return; + const utterance = new SpeechSynthesisUtterance(txt); + const voices = speechSynthesis.getVoices(); + let voice = voices.find(voice => voice.voiceURI === chatConf.speech_lang); + if (!voice) { + voice = voices.find(voice => voice.lang === 'en-US'); + } + utterance.voice = voice; + currentIndex = idx; + utterance.lang = voice.lang; + // utterance.rate = 0.7; + // utterance.pitch = 1.1; + // utterance.volume = 1; + synth.speak(utterance); + amISpeaking = synth.speaking; + saybtn.innerHTML = setIcon('speaking'); + currentUtterance = utterance; + currentIndex = idx; + utterance.onend = () => { + saybtn.innerHTML = setIcon('voice'); + currentUtterance = null; + currentIndex = -1; + } + } + }) +} + +function copyToClipboard(text, btn) { + window.clearTimeout(window.__cpTimeout); + btn.innerHTML = setIcon('cpok'); + if (navigator.clipboard) { + navigator.clipboard.writeText(text); + } else { + var textarea = document.createElement('textarea'); + document.body.appendChild(textarea); + textarea.style.position = 'fixed'; + textarea.style.clip = 'rect(0 0 0 0)'; + textarea.style.top = '10px'; + textarea.value = text; + textarea.select(); + document.execCommand('copy', true); + document.body.removeChild(textarea); + } + window.__cpTimeout = setTimeout(() => { + btn.innerHTML = setIcon('copy'); + }, 1000); +} + +function focusOnInput() { + // This currently works because there is only a single `