Compare commits

..

18 Commits
v0.11.1 ... dev

Author SHA1 Message Date
lencx
2b85b607d0 Merge branch 'main' into dev 2023-03-07 19:28:47 +08:00
lencx
a274c4c8de chore: optim 2023-03-07 19:28:20 +08:00
lencx
ce0d8983eb Merge pull request #572 from tk103331/main 2023-03-06 16:28:08 +08:00
tk103331
fa96092728 fix:windows voices is empty 2023-03-06 12:48:22 +08:00
lencx
488e249193 Merge pull request #561 from lencx/dev 2023-03-05 12:45:10 +08:00
lencx
20adf43859 v0.12.0 2023-03-05 12:44:42 +08:00
lencx
29ffb116cb v0.12.0 2023-03-05 12:17:59 +08:00
lencx
4d761d805e action 2023-03-05 12:17:50 +08:00
lencx
e5299986c0 readme 2023-03-05 12:17:01 +08:00
lencx
6c899634c2 chore: tts 2023-03-05 11:45:52 +08:00
lencx
e3e0c86aaf feat: autofocus (#550) 2023-03-05 10:12:46 +08:00
lencx
ce395e092b feat: tts (##534) 2023-03-05 10:10:42 +08:00
lencx
3176d9081d feat: tts (##534) 2023-03-05 08:51:27 +08:00
lencx
e2969b07e8 Merge pull request #549 from lencx/dev 2023-03-04 10:37:56 +08:00
lencx
15ad1c023d readme 2023-03-04 10:36:39 +08:00
lencx
cf5b93fbf0 Merge pull request #548 from lencx/dev 2023-03-04 10:28:07 +08:00
lencx
3fd46cbed1 chore: action 2023-03-04 10:27:31 +08:00
lencx
3831337767 Merge pull request #547 from lencx/dev 2023-03-04 10:15:15 +08:00
14 changed files with 522 additions and 399 deletions

View File

@@ -81,14 +81,6 @@ jobs:
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
- name: fix macos install
if: matrix.os == 'macos-latest'
run: |
tar -xzf ./target/${{ matrix.target }}/release/bundle/macos/ChatGPT.app.tar.gz
xattr -r -d com.apple.quarantine ChatGPT.app
rm -rf ./target/${{ matrix.target }}/release/bundle/macos/ChatGPT.app.tar.gz
tar -czf ./target/${{ matrix.target }}/release/bundle/macos/ChatGPT.app.tar.gz ChatGPT.app
- uses: actions/upload-artifact@v3
if: matrix.os == 'ubuntu-latest'
with:
@@ -202,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 }}

View File

@@ -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
@@ -62,10 +62,16 @@
cask "chatgpt", args: { "no-quarantine": true }
```
如果在 macOS 上安装软件时遇到 `“ChatGPT” is damaged and can't be opened. You should move it to the Trash.` 错误消息,可能是由于 macOS 安全设置的限制导致的。为了解决此问题,请在终端尝试以下命令:
```bash
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` 运行失败时可以尝试它**
<!-- tr-download-end -->

View File

@@ -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
@@ -71,10 +71,16 @@
cask "chatgpt", args: { "no-quarantine": true }
```
**If you encounter the error message `"ChatGPT" is damaged and can't be opened. You should move it to the Trash`. while installing software on macOS, it may be due to security settings restrictions in macOS. To solve this problem, please try the following command in Terminal:**
```bash
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
<!-- tr-download-end -->
@@ -103,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

View File

@@ -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:

View File

@@ -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"

View File

@@ -22,8 +22,8 @@ pub fn init() -> Menu {
#[cfg(target_os = "macos")]
MenuItem::About(name.into(), AboutMetadata::default()).into(),
#[cfg(not(target_os = "macos"))]
CustomMenuItem::new("about".to_string(), "About ChatGPT").into(),
CustomMenuItem::new("check_update".to_string(), "Check for Updates").into(),
CustomMenuItem::new("about", "About ChatGPT").into(),
CustomMenuItem::new("check_update", "Check for Updates").into(),
MenuItem::Services.into(),
MenuItem::Hide.into(),
MenuItem::HideOthers.into(),
@@ -33,25 +33,24 @@ pub fn init() -> Menu {
]),
);
let stay_on_top =
CustomMenuItem::new("stay_on_top".to_string(), "Stay On Top").accelerator("CmdOrCtrl+T");
let stay_on_top = CustomMenuItem::new("stay_on_top", "Stay On Top").accelerator("CmdOrCtrl+T");
let stay_on_top_menu = if app_conf.stay_on_top {
stay_on_top.selected()
} else {
stay_on_top
};
let theme_light = CustomMenuItem::new("theme_light".to_string(), "Light");
let theme_dark = CustomMenuItem::new("theme_dark".to_string(), "Dark");
let theme_system = CustomMenuItem::new("theme_system".to_string(), "System");
let theme_light = CustomMenuItem::new("theme_light", "Light");
let theme_dark = CustomMenuItem::new("theme_dark", "Dark");
let theme_system = CustomMenuItem::new("theme_system", "System");
let is_dark = app_conf.clone().theme_check("dark");
let is_system = app_conf.clone().theme_check("system");
let update_prompt = CustomMenuItem::new("update_prompt".to_string(), "Prompt");
let update_silent = CustomMenuItem::new("update_silent".to_string(), "Silent");
// let _update_disable = CustomMenuItem::new("update_disable".to_string(), "Disable");
let update_prompt = CustomMenuItem::new("update_prompt", "Prompt");
let update_silent = CustomMenuItem::new("update_silent", "Silent");
// let _update_disable = CustomMenuItem::new("update_disable", "Disable");
let popup_search = CustomMenuItem::new("popup_search".to_string(), "Pop-up Search");
let popup_search = CustomMenuItem::new("popup_search", "Pop-up Search");
let popup_search_menu = if app_conf.popup_search {
popup_search.selected()
} else {
@@ -59,7 +58,7 @@ pub fn init() -> Menu {
};
#[cfg(target_os = "macos")]
let titlebar = CustomMenuItem::new("titlebar".to_string(), "Titlebar").accelerator("CmdOrCtrl+B");
let titlebar = CustomMenuItem::new("titlebar", "Titlebar").accelerator("CmdOrCtrl+B");
#[cfg(target_os = "macos")]
let titlebar_menu = if app_conf.titlebar {
titlebar.selected()
@@ -67,7 +66,7 @@ pub fn init() -> Menu {
titlebar
};
let system_tray = CustomMenuItem::new("system_tray".to_string(), "System Tray");
let system_tray = CustomMenuItem::new("system_tray", "System Tray");
let system_tray_menu = if app_conf.tray {
system_tray.selected()
} else {
@@ -78,7 +77,7 @@ pub fn init() -> Menu {
let preferences_menu = Submenu::new(
"Preferences",
Menu::with_items([
CustomMenuItem::new("control_center".to_string(), "Control Center")
CustomMenuItem::new("control_center", "Control Center")
.accelerator("CmdOrCtrl+Shift+P")
.into(),
MenuItem::Separator.into(),
@@ -86,9 +85,9 @@ pub fn init() -> Menu {
#[cfg(target_os = "macos")]
titlebar_menu.into(),
#[cfg(target_os = "macos")]
CustomMenuItem::new("hide_dock_icon".to_string(), "Hide Dock Icon").into(),
CustomMenuItem::new("hide_dock_icon", "Hide Dock Icon").into(),
system_tray_menu.into(),
CustomMenuItem::new("inject_script".to_string(), "Inject Script")
CustomMenuItem::new("inject_script", "Inject Script")
.accelerator("CmdOrCtrl+J")
.into(),
MenuItem::Separator.into(),
@@ -133,18 +132,18 @@ pub fn init() -> Menu {
.into(),
MenuItem::Separator.into(),
popup_search_menu.into(),
CustomMenuItem::new("sync_prompts".to_string(), "Sync Prompts").into(),
CustomMenuItem::new("sync_prompts", "Sync Prompts").into(),
MenuItem::Separator.into(),
CustomMenuItem::new("go_conf".to_string(), "Go to Config")
CustomMenuItem::new("go_conf", "Go to Config")
.accelerator("CmdOrCtrl+Shift+G")
.into(),
CustomMenuItem::new("restart".to_string(), "Restart ChatGPT")
CustomMenuItem::new("restart", "Restart ChatGPT")
.accelerator("CmdOrCtrl+Shift+R")
.into(),
CustomMenuItem::new("clear_conf".to_string(), "Clear Config").into(),
CustomMenuItem::new("clear_conf", "Clear Config").into(),
MenuItem::Separator.into(),
CustomMenuItem::new("nofwl".to_string(), "NoFWL Desktop Application").into(),
CustomMenuItem::new("buy_coffee".to_string(), "Buy lencx a coffee").into(),
CustomMenuItem::new("nofwl", "NoFWL Desktop Application").into(),
CustomMenuItem::new("buy_coffee", "Buy lencx a coffee").into(),
]),
);
@@ -163,38 +162,28 @@ pub fn init() -> Menu {
let view_menu = Submenu::new(
"View",
Menu::new()
.add_item(CustomMenuItem::new("go_back".to_string(), "Go Back").accelerator("CmdOrCtrl+["))
.add_item(CustomMenuItem::new("go_back", "Go Back").accelerator("CmdOrCtrl+["))
.add_item(CustomMenuItem::new("go_forward", "Go Forward").accelerator("CmdOrCtrl+]"))
.add_item(
CustomMenuItem::new("go_forward".to_string(), "Go Forward").accelerator("CmdOrCtrl+]"),
CustomMenuItem::new("scroll_top", "Scroll to Top of Screen").accelerator("CmdOrCtrl+Up"),
)
.add_item(
CustomMenuItem::new("scroll_top".to_string(), "Scroll to Top of Screen")
.accelerator("CmdOrCtrl+Up"),
)
.add_item(
CustomMenuItem::new("scroll_bottom".to_string(), "Scroll to Bottom of Screen")
CustomMenuItem::new("scroll_bottom", "Scroll to Bottom of Screen")
.accelerator("CmdOrCtrl+Down"),
)
.add_native_item(MenuItem::Separator)
.add_item(
CustomMenuItem::new("zoom_0".to_string(), "Zoom to Actual Size").accelerator("CmdOrCtrl+0"),
)
.add_item(CustomMenuItem::new("zoom_out".to_string(), "Zoom Out").accelerator("CmdOrCtrl+-"))
.add_item(CustomMenuItem::new("zoom_in".to_string(), "Zoom In").accelerator("CmdOrCtrl+Plus"))
.add_item(CustomMenuItem::new("zoom_0", "Zoom to Actual Size").accelerator("CmdOrCtrl+0"))
.add_item(CustomMenuItem::new("zoom_out", "Zoom Out").accelerator("CmdOrCtrl+-"))
.add_item(CustomMenuItem::new("zoom_in", "Zoom In").accelerator("CmdOrCtrl+Plus"))
.add_native_item(MenuItem::Separator)
.add_item(
CustomMenuItem::new("reload".to_string(), "Refresh the Screen").accelerator("CmdOrCtrl+R"),
),
.add_item(CustomMenuItem::new("reload", "Refresh the Screen").accelerator("CmdOrCtrl+R")),
);
let window_menu = Submenu::new(
"Window",
Menu::new()
.add_item(CustomMenuItem::new(
"app_website".to_string(),
"ChatGPT User's Guide",
))
.add_item(CustomMenuItem::new("dalle2".to_string(), "DALL·E 2"))
.add_item(CustomMenuItem::new("app_website", "ChatGPT User's Guide"))
.add_item(CustomMenuItem::new("dalle2", "DALL·E 2"))
.add_native_item(MenuItem::Separator)
.add_native_item(MenuItem::Minimize)
.add_native_item(MenuItem::Zoom),
@@ -203,15 +192,11 @@ pub fn init() -> Menu {
let help_menu = Submenu::new(
"Help",
Menu::new()
.add_item(CustomMenuItem::new(
"chatgpt_log".to_string(),
"ChatGPT Log",
))
.add_item(CustomMenuItem::new("update_log".to_string(), "Update Log"))
.add_item(CustomMenuItem::new("report_bug".to_string(), "Report Bug"))
.add_item(CustomMenuItem::new("chatgpt_log", "ChatGPT Log"))
.add_item(CustomMenuItem::new("update_log", "Update Log"))
.add_item(CustomMenuItem::new("report_bug", "Report Bug"))
.add_item(
CustomMenuItem::new("dev_tools".to_string(), "Toggle Developer Tools")
.accelerator("CmdOrCtrl+Shift+I"),
CustomMenuItem::new("dev_tools", "Toggle Developer Tools").accelerator("CmdOrCtrl+Shift+I"),
),
);
@@ -248,7 +233,7 @@ pub fn menu_handler(event: WindowMenuEvent<tauri::Wry>) {
// Preferences
"control_center" => window::cmd::control_window(app),
"restart" => tauri::api::process::restart(&app.env()),
"inject_script" => open(&app, script_path),
"inject_script" => open(&app, &script_path),
"go_conf" => utils::open_file(utils::app_root()),
"clear_conf" => utils::clear_conf(&app),
"app_website" => window::cmd::wa_window(
@@ -258,8 +243,8 @@ pub fn menu_handler(event: WindowMenuEvent<tauri::Wry>) {
conf::APP_WEBSITE.into(),
None,
),
"nofwl" => open(&app, conf::NOFWL_APP.to_string()),
"buy_coffee" => open(&app, conf::BUY_COFFEE.to_string()),
"nofwl" => open(&app, conf::NOFWL_APP),
"buy_coffee" => open(&app, conf::BUY_COFFEE),
"popup_search" => {
let app_conf = AppConf::read();
let popup_search = !app_conf.popup_search;
@@ -394,8 +379,8 @@ pub fn menu_handler(event: WindowMenuEvent<tauri::Wry>) {
.unwrap(),
// Help
"chatgpt_log" => utils::open_file(utils::app_root().join("chatgpt.log")),
"update_log" => open(&app, conf::UPDATE_LOG_URL.to_string()),
"report_bug" => open(&app, conf::ISSUES_URL.to_string()),
"update_log" => open(&app, conf::UPDATE_LOG_URL),
"report_bug" => open(&app, conf::ISSUES_URL),
"dev_tools" => {
win.open_devtools();
win.close_devtools();
@@ -408,41 +393,29 @@ pub fn menu_handler(event: WindowMenuEvent<tauri::Wry>) {
pub fn tray_menu() -> SystemTray {
if cfg!(target_os = "macos") {
let mut tray_menu = SystemTrayMenu::new()
.add_item(CustomMenuItem::new(
"control_center".to_string(),
"Control Center",
))
.add_item(CustomMenuItem::new("control_center", "Control Center"))
.add_native_item(SystemTrayMenuItem::Separator);
if AppConf::read().hide_dock_icon {
tray_menu = tray_menu.add_item(CustomMenuItem::new(
"show_dock_icon".to_string(),
"Show Dock Icon",
));
tray_menu = tray_menu.add_item(CustomMenuItem::new("show_dock_icon", "Show Dock Icon"));
} else {
tray_menu = tray_menu
.add_item(CustomMenuItem::new(
"hide_dock_icon".to_string(),
"Hide Dock Icon",
))
.add_item(CustomMenuItem::new("show_core".to_string(), "Show Window"));
.add_item(CustomMenuItem::new("hide_dock_icon", "Hide Dock Icon"))
.add_item(CustomMenuItem::new("show_core", "Show Window"));
}
SystemTray::new().with_menu(
tray_menu
.add_native_item(SystemTrayMenuItem::Separator)
.add_item(CustomMenuItem::new("quit".to_string(), "Quit")),
.add_item(CustomMenuItem::new("quit", "Quit")),
)
} else {
SystemTray::new().with_menu(
SystemTrayMenu::new()
.add_item(CustomMenuItem::new(
"control_center".to_string(),
"Control Center",
))
.add_item(CustomMenuItem::new("show_core".to_string(), "Show Window"))
.add_item(CustomMenuItem::new("control_center", "Control Center"))
.add_item(CustomMenuItem::new("show_core", "Show Window"))
.add_native_item(SystemTrayMenuItem::Separator)
.add_item(CustomMenuItem::new("quit".to_string(), "Quit")),
.add_item(CustomMenuItem::new("quit", "Quit")),
)
}
}
@@ -508,6 +481,6 @@ pub fn tray_handler(handle: &AppHandle, event: SystemTrayEvent) {
}
}
pub fn open(app: &AppHandle, path: String) {
pub fn open(app: &AppHandle, path: &str) {
tauri::api::shell::open(&app.shell_scope(), path, None).unwrap();
}

View File

@@ -88,6 +88,7 @@ pub fn init(app: &mut App) -> std::result::Result<(), Box<dyn std::error::Error>
.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();

View File

@@ -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"))
}

View File

@@ -40,6 +40,7 @@ pub_struct!(AppConf {
save_window_state: bool,
global_shortcut: Option<String>,
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,

124
src-tauri/src/scripts/chat.js vendored Normal file
View File

@@ -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 `<textarea>` element on the ChatGPT UI page.
document.getElementsByTagName("textarea")[0].focus();
}
function setIcon(type) {
return {
copy: `<svg class="chatappico copy" stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="h-4 w-4" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"></path><rect x="8" y="2" width="8" height="4" rx="1" ry="1"></rect></svg>`,
cpok: `<svg class="chatappico cpok" viewBox="0 0 24 24"><g fill="none" stroke="#10a37f" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><rect width="8" height="4" x="8" y="2" rx="1" ry="1"/><path d="M8 4H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-2M16 4h2a2 2 0 0 1 2 2v4m1 4H11"/><path d="m15 10l-4 4l4 4"/></g></svg>`,
voice: `<svg class="chatappico voice" viewBox="0 0 1024 1024"><path d="M542.923802 202.113207c-5.110391 0-10.717086 1.186012-16.572444 3.739161L360.043634 312.714188l-83.057671 0c-46.109154 0-83.433224 36.917818-83.433224 83.121116l0 166.646438c0 45.952588 36.950564 83.153862 83.433224 83.153862l83.057671 0 166.307723 106.829074c23.550369 10.218736 41.745776-0.717338 41.745776-23.898293L568.097134 229.687216C568.096111 212.426087 557.753555 202.113207 542.923802 202.113207z" fill="currentColor"></path><path d="M794.154683 314.39548c-16.758686-28.537963-33.771151-48.258097-45.610804-58.882062-3.986801-3.489474-8.972349-5.233188-13.833053-5.233188-5.79396 0-11.464099 2.337231-15.57779 6.91448-7.662517 8.631588-6.976902 21.808702 1.620917 29.410843 1.994424 1.744737 5.856381 5.700839 11.154038 11.777231 9.033747 10.437723 18.006096 22.774703 26.419719 37.072337 24.235984 41.033555 38.755676 89.011266 38.755676 143.688563 0 54.705949-14.519692 102.651938-38.755676 143.810337-8.414647 14.20656-17.448394 26.668383-26.484188 37.07336-5.234211 6.076392-9.096169 10.033517-11.149944 11.778254-8.538467 7.603165-9.224082 20.717857-1.683339 29.40982 7.599072 8.473999 20.807908 9.222035 29.40982 1.650593 11.900028-10.562567 28.910447-30.252001 45.732577-58.850339 27.79095-47.078225 44.490284-102.3122 44.490284-164.872025C838.708412 416.646282 821.946656 361.470635 794.154683 314.39548z" fill="currentColor"></path><path d="M690.846806 377.360534c-8.723685-17.790178-17.698081-30.2827-24.301476-37.260625-4.111644-4.3951-9.595542-6.544043-15.139815-6.544043-5.110391 0-10.159384 1.774413-14.270005 5.54632-8.350179 7.881504-8.847505 20.99722-0.997724 29.471219 3.927449 4.112668 10.468422 13.304004 17.448394 27.199479 11.587919 23.77038 18.567891 51.559283 18.567891 83.370803 0 31.80845-6.978948 59.72322-18.567891 83.400478-6.978948 13.892405-13.520945 23.052019-17.448394 27.259854-7.850805 8.410554-7.353478 21.559015 0.997724 29.440519 8.473999 7.882528 21.559015 7.353478 29.474288-1.025353 6.53995-7.011694 15.513322-19.440771 24.238031-37.356816 14.393825-29.189809 22.992667-63.243393 22.992667-101.781104C713.839473 440.603927 705.241654 406.583089 690.846806 377.360534z" fill="currentColor"></path></svg>`,
speaking: `<svg class="chatappico voice" viewBox="0 0 1024 1024"><path d="M542.923802 202.113207c-5.110391 0-10.717086 1.186012-16.572444 3.739161L360.043634 312.714188l-83.057671 0c-46.109154 0-83.433224 36.917818-83.433224 83.121116l0 166.646438c0 45.952588 36.950564 83.153862 83.433224 83.153862l83.057671 0 166.307723 106.829074c23.550369 10.218736 41.745776-0.717338 41.745776-23.898293L568.097134 229.687216C568.096111 212.426087 557.753555 202.113207 542.923802 202.113207z" fill="#10a37f"></path><path d="M794.154683 314.39548c-16.758686-28.537963-33.771151-48.258097-45.610804-58.882062-3.986801-3.489474-8.972349-5.233188-13.833053-5.233188-5.79396 0-11.464099 2.337231-15.57779 6.91448-7.662517 8.631588-6.976902 21.808702 1.620917 29.410843 1.994424 1.744737 5.856381 5.700839 11.154038 11.777231 9.033747 10.437723 18.006096 22.774703 26.419719 37.072337 24.235984 41.033555 38.755676 89.011266 38.755676 143.688563 0 54.705949-14.519692 102.651938-38.755676 143.810337-8.414647 14.20656-17.448394 26.668383-26.484188 37.07336-5.234211 6.076392-9.096169 10.033517-11.149944 11.778254-8.538467 7.603165-9.224082 20.717857-1.683339 29.40982 7.599072 8.473999 20.807908 9.222035 29.40982 1.650593 11.900028-10.562567 28.910447-30.252001 45.732577-58.850339 27.79095-47.078225 44.490284-102.3122 44.490284-164.872025C838.708412 416.646282 821.946656 361.470635 794.154683 314.39548z" fill="#10a37f"></path><path d="M690.846806 377.360534c-8.723685-17.790178-17.698081-30.2827-24.301476-37.260625-4.111644-4.3951-9.595542-6.544043-15.139815-6.544043-5.110391 0-10.159384 1.774413-14.270005 5.54632-8.350179 7.881504-8.847505 20.99722-0.997724 29.471219 3.927449 4.112668 10.468422 13.304004 17.448394 27.199479 11.587919 23.77038 18.567891 51.559283 18.567891 83.370803 0 31.80845-6.978948 59.72322-18.567891 83.400478-6.978948 13.892405-13.520945 23.052019-17.448394 27.259854-7.850805 8.410554-7.353478 21.559015 0.997724 29.440519 8.473999 7.882528 21.559015 7.353478 29.474288-1.025353 6.53995-7.011694 15.513322-19.440771 24.238031-37.356816 14.393825-29.189809 22.992667-63.243393 22.992667-101.781104C713.839473 440.603927 705.241654 406.583089 690.846806 377.360534z" fill="#10a37f"></path></svg>`,
}[type];
}
if (
document.readyState === "complete" ||
document.readyState === "interactive"
) {
init();
} else {
document.addEventListener("DOMContentLoaded", init);
}

View File

@@ -83,6 +83,16 @@ function init() {
width: 16px;
height: 16px;
}
.chatappico.refresh {
width: 22px;
height: 22px;
}
#download-markdown-button,
#download-png-button,
#download-pdf-button,
#refresh-page-button {
border: none;
}
@media screen and (max-width: 767px) {
#download-png-button, #download-pdf-button, #download-html-button {
display: none;

View File

@@ -15,6 +15,7 @@ async function init() {
if (!actionsArea) {
return;
}
if (shouldAddButtons(actionsArea)) {
let TryAgainButton = actionsArea.querySelector("button");
if (!TryAgainButton) {
@@ -23,302 +24,278 @@ async function init() {
TryAgainButton = parentNode.querySelector("button");
}
addActionsButtons(actionsArea, TryAgainButton, chatConf);
copyBtns();
} else if (shouldRemoveButtons()) {
removeButtons();
}
}, 1000);
const Format = {
PNG: "png",
PDF: "pdf",
};
function shouldRemoveButtons() {
if (document.querySelector("form .text-2xl")) {
return true;
}
return false;
}
function shouldAddButtons(actionsArea) {
// first, check if there's a "Try Again" button and no other buttons
const buttons = actionsArea.querySelectorAll("button");
const hasTryAgainButton = Array.from(buttons).some((button) => {
return !/download-/.test(button.id);
});
const stopBtn = buttons?.[0]?.innerText;
if (/Stop generating/ig.test(stopBtn)) {
return false;
}
if (buttons.length === 2 && (/Regenerate response/ig.test(stopBtn) || buttons[1].innerText === '')) {
return true;
}
if (hasTryAgainButton && buttons.length === 1) {
return true;
}
// otherwise, check if open screen is not visible
const isOpenScreen = document.querySelector("h1.text-4xl");
if (isOpenScreen) {
return false;
}
// check if the conversation is finished and there are no share buttons
const finishedConversation = document.querySelector("form button>svg");
const hasShareButtons = actionsArea.querySelectorAll("button[share-ext]");
if (finishedConversation && !hasShareButtons.length) {
return true;
}
return false;
}
function removeButtons() {
const downloadPngButton = document.getElementById("download-png-button");
const downloadPdfButton = document.getElementById("download-pdf-button");
const downloadMdButton = document.getElementById("download-markdown-button");
const refreshButton = document.getElementById("refresh-page-button");
if (downloadPngButton) {
downloadPngButton.remove();
}
if (downloadPdfButton) {
downloadPdfButton.remove();
}
if (downloadPdfButton) {
downloadMdButton.remove();
}
if (refreshButton) {
refreshButton.remove();
}
}
function addActionsButtons(actionsArea, TryAgainButton) {
// Export markdown
const exportMd = TryAgainButton.cloneNode(true);
exportMd.id = "download-markdown-button";
exportMd.setAttribute("share-ext", "true");
exportMd.title = "Export Markdown";
exportMd.innerHTML = setIcon('md');
exportMd.onclick = () => {
exportMarkdown();
};
actionsArea.appendChild(exportMd);
// Generate PNG
const downloadPngButton = TryAgainButton.cloneNode(true);
downloadPngButton.id = "download-png-button";
downloadPngButton.setAttribute("share-ext", "true");
downloadPngButton.title = "Generate PNG";
downloadPngButton.innerHTML = setIcon('png');
downloadPngButton.onclick = () => {
downloadThread();
};
actionsArea.appendChild(downloadPngButton);
// Generate PDF
const downloadPdfButton = TryAgainButton.cloneNode(true);
downloadPdfButton.id = "download-pdf-button";
downloadPdfButton.setAttribute("share-ext", "true");
downloadPdfButton.title = "Download PDF";
downloadPdfButton.innerHTML = setIcon('pdf');
downloadPdfButton.onclick = () => {
downloadThread({ as: Format.PDF });
};
actionsArea.appendChild(downloadPdfButton);
// Refresh
const refreshButton = TryAgainButton.cloneNode(true);
refreshButton.id = "refresh-page-button";
refreshButton.title = "Refresh the Page";
refreshButton.innerHTML = setIcon('refresh');
refreshButton.onclick = () => {
window.location.reload();
};
actionsArea.appendChild(refreshButton);
}
async function exportMarkdown() {
const content = Array.from(document.querySelectorAll('main .items-center>div')).map(i => {
let j = i.cloneNode(true);
if (/dark\:bg-gray-800/.test(i.getAttribute('class'))) {
j.innerHTML = `<blockquote>${i.innerHTML}</blockquote>`;
}
return j.innerHTML;
}).join('');
const data = ExportMD.turndown(content);
const { id, filename } = getName();
await invoke('save_file', { name: `notes/${id}.md`, content: data });
await invoke('download_list', { pathname: 'chat.notes.json', filename, id, dir: 'notes' });
}
function downloadThread({ as = Format.PNG } = {}) {
const elements = new Elements();
elements.fixLocation();
const pixelRatio = window.devicePixelRatio;
const minRatio = as === Format.PDF ? 2 : 2.5;
window.devicePixelRatio = Math.max(pixelRatio, minRatio);
html2canvas(elements.thread, {
letterRendering: true,
}).then(async function (canvas) {
elements.restoreLocation();
window.devicePixelRatio = pixelRatio;
const imgData = canvas.toDataURL("image/png");
requestAnimationFrame(() => {
if (as === Format.PDF) {
return handlePdf(imgData, canvas, pixelRatio);
} else {
handleImg(imgData);
}
});
});
}
async function handleImg(imgData) {
const binaryData = atob(imgData.split("base64,")[1]);
const data = [];
for (let i = 0; i < binaryData.length; i++) {
data.push(binaryData.charCodeAt(i));
}
const { pathname, id, filename } = getName();
await invoke('download', { name: `download/img/${id}.png`, blob: data });
await invoke('download_list', { pathname, filename, id, dir: 'download' });
}
async function handlePdf(imgData, canvas, pixelRatio) {
const { jsPDF } = window.jspdf;
const orientation = canvas.width > canvas.height ? "l" : "p";
var pdf = new jsPDF(orientation, "pt", [
canvas.width / pixelRatio,
canvas.height / pixelRatio,
]);
var pdfWidth = pdf.internal.pageSize.getWidth();
var pdfHeight = pdf.internal.pageSize.getHeight();
pdf.addImage(imgData, "PNG", 0, 0, pdfWidth, pdfHeight, '', 'FAST');
const { pathname, id, filename } = getName();
const data = pdf.__private__.getArrayBuffer(pdf.__private__.buildDocument());
await invoke('download', { name: `download/pdf/${id}.pdf`, blob: Array.from(new Uint8Array(data)) });
await invoke('download_list', { pathname, filename, id, dir: 'download' });
}
function getName() {
const id = window.crypto.getRandomValues(new Uint32Array(1))[0].toString(36);
const name = document.querySelector('nav .overflow-y-auto a.hover\\:bg-gray-800')?.innerText?.trim() || '';
return { filename: name ? name : id, id, pathname: 'chat.download.json' };
}
class Elements {
constructor() {
this.init();
}
init() {
// this.threadWrapper = document.querySelector(".cdfdFe");
this.spacer = document.querySelector("[class*='h-48'].w-full.flex-shrink-0");
this.thread = document.querySelector(
"[class*='react-scroll-to-bottom']>[class*='react-scroll-to-bottom']>div"
);
// fix: old chat https://github.com/lencx/ChatGPT/issues/185
if (!this.thread) {
this.thread = document.querySelector("main .overflow-y-auto");
}
// h-full overflow-y-auto
this.positionForm = document.querySelector("form").parentNode;
// this.styledThread = document.querySelector("main");
// this.threadContent = document.querySelector(".gAnhyd");
this.scroller = Array.from(
document.querySelectorAll('[class*="react-scroll-to"]')
).filter((el) => el.classList.contains("h-full"))[0];
// fix: old chat
if (!this.scroller) {
this.scroller = document.querySelector('main .overflow-y-auto');
}
this.hiddens = Array.from(document.querySelectorAll(".overflow-hidden"));
this.images = Array.from(document.querySelectorAll("img[srcset]"));
}
fixLocation() {
this.hiddens.forEach((el) => {
el.classList.remove("overflow-hidden");
});
this.spacer.style.display = "none";
this.thread.style.maxWidth = "960px";
this.thread.style.marginInline = "auto";
this.positionForm.style.display = "none";
this.scroller.classList.remove("h-full");
this.scroller.style.minHeight = "100vh";
this.images.forEach((img) => {
const srcset = img.getAttribute("srcset");
img.setAttribute("srcset_old", srcset);
img.setAttribute("srcset", "");
});
//Fix to the text shifting down when generating the canvas
document.body.style.lineHeight = "0.5";
}
restoreLocation() {
this.hiddens.forEach((el) => {
el.classList.add("overflow-hidden");
});
this.spacer.style.display = null;
this.thread.style.maxWidth = null;
this.thread.style.marginInline = null;
this.positionForm.style.display = null;
this.scroller.classList.add("h-full");
this.scroller.style.minHeight = null;
this.images.forEach((img) => {
const srcset = img.getAttribute("srcset_old");
img.setAttribute("srcset", srcset);
img.setAttribute("srcset_old", "");
});
document.body.style.lineHeight = null;
}
}
function setIcon(type) {
return {
// link: `<svg class="chatappico" viewBox="0 0 1024 1024"><path d="M1007.382 379.672L655.374 75.702C624.562 49.092 576 70.694 576 112.03v160.106C254.742 275.814 0 340.2 0 644.652c0 122.882 79.162 244.618 166.666 308.264 27.306 19.862 66.222-5.066 56.154-37.262C132.132 625.628 265.834 548.632 576 544.17V720c0 41.4 48.6 62.906 79.374 36.328l352.008-304c22.142-19.124 22.172-53.506 0-72.656z" p-id="8506" fill="currentColor"></path></svg>`,
png: `<svg class="chatappico" viewBox="0 0 1070 1024"><path d="M981.783273 0H85.224727C38.353455 0 0 35.374545 0 83.083636v844.893091c0 47.616 38.353455 86.574545 85.178182 86.574546h903.633454c46.917818 0 81.733818-38.958545 81.733819-86.574546V83.083636C1070.592 35.374545 1028.701091 0 981.783273 0zM335.825455 135.912727c74.193455 0 134.330182 60.974545 134.330181 136.285091 0 75.170909-60.136727 136.192-134.330181 136.192-74.286545 0-134.516364-61.021091-134.516364-136.192 0-75.264 60.229818-136.285091 134.516364-136.285091z m-161.512728 745.937455a41.890909 41.890909 0 0 1-27.648-10.379637 43.752727 43.752727 0 0 1-4.654545-61.067636l198.097454-255.162182a42.123636 42.123636 0 0 1 57.716364-6.702545l116.549818 128.139636 286.906182-352.814545c14.615273-18.711273 90.251636-106.775273 135.866182-6.935273 0.093091-0.093091 0.093091 112.965818 0.232727 247.761455 0.093091 140.8 0.093091 317.067636 0.093091 317.067636-1.024-0.093091-762.740364 0.093091-763.112727 0.093091z" fill="currentColor"></path></svg>`,
pdf: `<svg class="chatappico pdf" viewBox="0 0 1024 1024"><path d="M821.457602 118.382249H205.725895c-48.378584 0-87.959995 39.583368-87.959996 87.963909v615.731707c0 48.378584 39.581411 87.959995 87.959996 87.959996h615.733664c48.380541 0 87.961952-39.581411 87.961952-87.959996V206.346158c-0.001957-48.378584-39.583368-87.963909-87.963909-87.963909zM493.962468 457.544987c-10.112054 32.545237-21.72487 82.872662-38.806571 124.248336-8.806957 22.378397-8.380404 18.480717-15.001764 32.609808l5.71738-1.851007c58.760658-16.443827 99.901532-20.519564 138.162194-27.561607-7.67796-6.06371-14.350194-10.751884-19.631237-15.586807-26.287817-29.101504-35.464584-34.570387-70.440002-111.862636v0.003913z m288.36767 186.413594c-7.476424 8.356924-20.670227 13.191847-40.019704 13.191847-33.427694 0-63.808858-9.229597-107.79277-31.660824-75.648648 8.356924-156.097 17.214754-201.399704 31.729308-2.199293 0.876587-4.832967 1.759043-7.916674 3.077836-54.536215 93.237125-95.031389 132.767663-130.621199 131.19646-11.286054-0.49895-27.694661-7.044-32.973748-10.11988l-6.52157-6.196764-2.29517-4.353583c-3.07588-7.91863-3.954423-15.395054-2.197337-23.751977 4.838837-23.309771 29.907651-60.251638 82.686779-93.237126 8.356924-6.159587 27.430511-15.897917 45.020944-24.25484 13.311204-21.177004 19.45905-34.744531 36.341171-72.259702 19.102937-45.324228 36.505531-99.492589 47.500041-138.191543v-0.44025c-16.267727-53.219378-25.945401-89.310095-9.67376-147.80856 3.958337-16.71189 18.46702-33.864031 34.748444-33.864031h10.552304c10.115967 0 19.791684 3.520043 26.829814 10.552304 29.029107 29.031064 15.39114 103.824649 0.8805 162.323113-0.8805 2.63563-1.322707 4.832967-1.761 6.153717 17.59239 49.697378 45.400538 98.774492 73.108895 121.647926 11.436717 8.791304 22.638634 18.899444 36.71098 26.814161 19.791684-2.20125 37.517128-4.11487 55.547812-4.11487 54.540128 0 87.525615 9.67963 100.279169 30.351814 4.400543 7.034217 6.595923 15.389184 5.281043 24.1844-0.44025 10.996467-4.39663 21.112434-12.31526 29.031064z m-27.796407-36.748157c-4.394673-4.398587-17.024957-16.936907-78.601259-16.936907-3.073923 0-10.622744-0.784623-14.57521 3.612007 32.104987 14.072347 62.830525 24.757704 83.058545 24.757703 3.083707 0 5.72325-0.442207 8.356923-0.876586h1.759044c2.20125-0.8805 3.520043-1.324663 3.960293-5.71738-0.87463-1.324663-1.757087-3.083707-3.958336-4.838837z m-387.124553 63.041845c-9.237424 5.27713-16.71189 10.112054-21.112433 13.634053-31.226444 28.586901-51.018128 57.616008-53.217422 74.331812 19.789727-6.59788 45.737084-35.626987 74.329855-87.961952v-0.003913z m125.574957-297.822284l2.197336-1.761c3.079793-14.072347 5.232127-29.189554 7.87167-38.869184l1.318794-7.036174c4.39663-25.070771 2.71781-39.720334-4.76057-50.272637l-6.59788-2.20125a57.381208 57.381208 0 0 0-3.079794 5.27713c-7.474467 18.47289-7.063567 55.283661 3.0524 94.865072l-0.001956-0.001957z" fill="currentColor"></path></svg>`,
md: `<svg class="chatappico md" viewBox="0 0 1024 1024" width="200" height="200"><path d="M128 128h768a42.666667 42.666667 0 0 1 42.666667 42.666667v682.666666a42.666667 42.666667 0 0 1-42.666667 42.666667H128a42.666667 42.666667 0 0 1-42.666667-42.666667V170.666667a42.666667 42.666667 0 0 1 42.666667-42.666667z m170.666667 533.333333v-170.666666l85.333333 85.333333 85.333333-85.333333v170.666666h85.333334v-298.666666h-85.333334l-85.333333 85.333333-85.333333-85.333333H213.333333v298.666666h85.333334z m469.333333-128v-170.666666h-85.333333v170.666666h-85.333334l128 128 128-128h-85.333333z" p-id="1381" fill="currentColor"></path></svg>`,
refresh: `<svg class="chatappico refresh" viewBox="0 0 1024 1024"><path d="M512 63.5C264.3 63.5 63.5 264.3 63.5 512S264.3 960.5 512 960.5 960.5 759.7 960.5 512 759.7 63.5 512 63.5zM198 509.6h87.6c0-136.3 102.3-243.4 233.7-238.5 43.8 0 82.8 14.6 121.7 34.1L597.2 349c-24.4-9.8-53.6-19.5-82.8-19.5-92.5 0-170.4 77.9-170.4 180.1h87.6L314.8 631.3 198 509.6z m540.3-0.1c0 131.4-102.2 243.4-228.8 243.4-43.8 0-82.8-19.4-121.7-38.9l43.8-43.8c24.4 9.8 53.6 19.5 82.8 19.5 92.5 0 170.4-77.9 170.4-180.1h-92.5l116.9-121.7L826 509.5h-87.7z" fill="currentColor"></path></svg>`,
}[type];
}
}
window.addEventListener('resize', init);
const Format = {
PNG: "png",
PDF: "pdf",
};
function shouldRemoveButtons() {
if (document.querySelector("form .text-2xl")) {
return true;
}
return false;
}
function shouldAddButtons(actionsArea) {
// first, check if there's a "Try Again" button and no other buttons
const buttons = actionsArea.querySelectorAll("button");
const hasTryAgainButton = Array.from(buttons).some((button) => {
return !/download-/.test(button.id);
});
const stopBtn = buttons?.[0]?.innerText;
if (/Stop generating/ig.test(stopBtn)) {
return false;
}
if (buttons.length === 2 && (/Regenerate response/ig.test(stopBtn) || buttons[1].innerText === '')) {
return true;
}
if (hasTryAgainButton && buttons.length === 1) {
return true;
}
// otherwise, check if open screen is not visible
const isOpenScreen = document.querySelector("h1.text-4xl");
if (isOpenScreen) {
return false;
}
// check if the conversation is finished and there are no share buttons
const finishedConversation = document.querySelector("form button>svg");
const hasShareButtons = actionsArea.querySelectorAll("button[share-ext]");
if (finishedConversation && !hasShareButtons.length) {
return true;
}
return false;
}
function removeButtons() {
const downloadButton = document.getElementById("download-png-button");
const downloadPdfButton = document.getElementById("download-pdf-button");
const downloadMdButton = document.getElementById("download-markdown-button");
if (downloadButton) {
downloadButton.remove();
}
if (downloadPdfButton) {
downloadPdfButton.remove();
}
if (downloadPdfButton) {
downloadMdButton.remove();
}
}
function addActionsButtons(actionsArea, TryAgainButton) {
const downloadButton = TryAgainButton.cloneNode(true);
// Export markdown
const exportMd = TryAgainButton.cloneNode(true);
exportMd.id = "download-markdown-button";
downloadButton.setAttribute("share-ext", "true");
exportMd.title = "Export Markdown";
exportMd.innerHTML = setIcon('md');
exportMd.onclick = () => {
exportMarkdown();
};
actionsArea.appendChild(exportMd);
// Generate PNG
downloadButton.id = "download-png-button";
downloadButton.setAttribute("share-ext", "true");
downloadButton.title = "Generate PNG";
downloadButton.innerHTML = setIcon('png');
downloadButton.onclick = () => {
downloadThread();
};
actionsArea.appendChild(downloadButton);
// Generate PDF
const downloadPdfButton = TryAgainButton.cloneNode(true);
downloadPdfButton.id = "download-pdf-button";
downloadButton.setAttribute("share-ext", "true");
downloadPdfButton.title = "Download PDF";
downloadPdfButton.innerHTML = setIcon('pdf');
downloadPdfButton.onclick = () => {
downloadThread({ as: Format.PDF });
};
actionsArea.appendChild(downloadPdfButton);
}
async function exportMarkdown() {
const content = Array.from(document.querySelectorAll('main .items-center>div')).map(i => {
let j = i.cloneNode(true);
if (/dark\:bg-gray-800/.test(i.getAttribute('class'))) {
j.innerHTML = `<blockquote>${i.innerHTML}</blockquote>`;
}
return j.innerHTML;
}).join('');
const data = ExportMD.turndown(content);
const { id, filename } = getName();
await invoke('save_file', { name: `notes/${id}.md`, content: data });
await invoke('download_list', { pathname: 'chat.notes.json', filename, id, dir: 'notes' });
}
function downloadThread({ as = Format.PNG } = {}) {
const elements = new Elements();
elements.fixLocation();
const pixelRatio = window.devicePixelRatio;
const minRatio = as === Format.PDF ? 2 : 2.5;
window.devicePixelRatio = Math.max(pixelRatio, minRatio);
html2canvas(elements.thread, {
letterRendering: true,
}).then(async function (canvas) {
elements.restoreLocation();
window.devicePixelRatio = pixelRatio;
const imgData = canvas.toDataURL("image/png");
requestAnimationFrame(() => {
if (as === Format.PDF) {
return handlePdf(imgData, canvas, pixelRatio);
} else {
handleImg(imgData);
}
});
});
}
async function handleImg(imgData) {
const binaryData = atob(imgData.split("base64,")[1]);
const data = [];
for (let i = 0; i < binaryData.length; i++) {
data.push(binaryData.charCodeAt(i));
}
const { pathname, id, filename } = getName();
await invoke('download', { name: `download/img/${id}.png`, blob: data });
await invoke('download_list', { pathname, filename, id, dir: 'download' });
}
async function handlePdf(imgData, canvas, pixelRatio) {
const { jsPDF } = window.jspdf;
const orientation = canvas.width > canvas.height ? "l" : "p";
var pdf = new jsPDF(orientation, "pt", [
canvas.width / pixelRatio,
canvas.height / pixelRatio,
]);
var pdfWidth = pdf.internal.pageSize.getWidth();
var pdfHeight = pdf.internal.pageSize.getHeight();
pdf.addImage(imgData, "PNG", 0, 0, pdfWidth, pdfHeight, '', 'FAST');
const { pathname, id, filename } = getName();
const data = pdf.__private__.getArrayBuffer(pdf.__private__.buildDocument());
await invoke('download', { name: `download/pdf/${id}.pdf`, blob: Array.from(new Uint8Array(data)) });
await invoke('download_list', { pathname, filename, id, dir: 'download' });
}
function getName() {
const id = window.crypto.getRandomValues(new Uint32Array(1))[0].toString(36);
const name = document.querySelector('nav .overflow-y-auto a.hover\\:bg-gray-800')?.innerText?.trim() || '';
return { filename: name ? name : id, id, pathname: 'chat.download.json' };
}
class Elements {
constructor() {
this.init();
}
init() {
// this.threadWrapper = document.querySelector(".cdfdFe");
this.spacer = document.querySelector("[class*='h-48'].w-full.flex-shrink-0");
this.thread = document.querySelector(
"[class*='react-scroll-to-bottom']>[class*='react-scroll-to-bottom']>div"
);
// fix: old chat https://github.com/lencx/ChatGPT/issues/185
if (!this.thread) {
this.thread = document.querySelector("main .overflow-y-auto");
}
// h-full overflow-y-auto
this.positionForm = document.querySelector("form").parentNode;
// this.styledThread = document.querySelector("main");
// this.threadContent = document.querySelector(".gAnhyd");
this.scroller = Array.from(
document.querySelectorAll('[class*="react-scroll-to"]')
).filter((el) => el.classList.contains("h-full"))[0];
// fix: old chat
if (!this.scroller) {
this.scroller = document.querySelector('main .overflow-y-auto');
}
this.hiddens = Array.from(document.querySelectorAll(".overflow-hidden"));
this.images = Array.from(document.querySelectorAll("img[srcset]"));
}
fixLocation() {
this.hiddens.forEach((el) => {
el.classList.remove("overflow-hidden");
});
this.spacer.style.display = "none";
this.thread.style.maxWidth = "960px";
this.thread.style.marginInline = "auto";
this.positionForm.style.display = "none";
this.scroller.classList.remove("h-full");
this.scroller.style.minHeight = "100vh";
this.images.forEach((img) => {
const srcset = img.getAttribute("srcset");
img.setAttribute("srcset_old", srcset);
img.setAttribute("srcset", "");
});
//Fix to the text shifting down when generating the canvas
document.body.style.lineHeight = "0.5";
}
restoreLocation() {
this.hiddens.forEach((el) => {
el.classList.add("overflow-hidden");
});
this.spacer.style.display = null;
this.thread.style.maxWidth = null;
this.thread.style.marginInline = null;
this.positionForm.style.display = null;
this.scroller.classList.add("h-full");
this.scroller.style.minHeight = null;
this.images.forEach((img) => {
const srcset = img.getAttribute("srcset_old");
img.setAttribute("srcset", srcset);
img.setAttribute("srcset_old", "");
});
document.body.style.lineHeight = null;
}
}
function setIcon(type) {
return {
// link: `<svg class="chatappico" viewBox="0 0 1024 1024"><path d="M1007.382 379.672L655.374 75.702C624.562 49.092 576 70.694 576 112.03v160.106C254.742 275.814 0 340.2 0 644.652c0 122.882 79.162 244.618 166.666 308.264 27.306 19.862 66.222-5.066 56.154-37.262C132.132 625.628 265.834 548.632 576 544.17V720c0 41.4 48.6 62.906 79.374 36.328l352.008-304c22.142-19.124 22.172-53.506 0-72.656z" p-id="8506" fill="currentColor"></path></svg>`,
png: `<svg class="chatappico" viewBox="0 0 1070 1024"><path d="M981.783273 0H85.224727C38.353455 0 0 35.374545 0 83.083636v844.893091c0 47.616 38.353455 86.574545 85.178182 86.574546h903.633454c46.917818 0 81.733818-38.958545 81.733819-86.574546V83.083636C1070.592 35.374545 1028.701091 0 981.783273 0zM335.825455 135.912727c74.193455 0 134.330182 60.974545 134.330181 136.285091 0 75.170909-60.136727 136.192-134.330181 136.192-74.286545 0-134.516364-61.021091-134.516364-136.192 0-75.264 60.229818-136.285091 134.516364-136.285091z m-161.512728 745.937455a41.890909 41.890909 0 0 1-27.648-10.379637 43.752727 43.752727 0 0 1-4.654545-61.067636l198.097454-255.162182a42.123636 42.123636 0 0 1 57.716364-6.702545l116.549818 128.139636 286.906182-352.814545c14.615273-18.711273 90.251636-106.775273 135.866182-6.935273 0.093091-0.093091 0.093091 112.965818 0.232727 247.761455 0.093091 140.8 0.093091 317.067636 0.093091 317.067636-1.024-0.093091-762.740364 0.093091-763.112727 0.093091z" fill="currentColor"></path></svg>`,
pdf: `<svg class="chatappico pdf" viewBox="0 0 1024 1024"><path d="M821.457602 118.382249H205.725895c-48.378584 0-87.959995 39.583368-87.959996 87.963909v615.731707c0 48.378584 39.581411 87.959995 87.959996 87.959996h615.733664c48.380541 0 87.961952-39.581411 87.961952-87.959996V206.346158c-0.001957-48.378584-39.583368-87.963909-87.963909-87.963909zM493.962468 457.544987c-10.112054 32.545237-21.72487 82.872662-38.806571 124.248336-8.806957 22.378397-8.380404 18.480717-15.001764 32.609808l5.71738-1.851007c58.760658-16.443827 99.901532-20.519564 138.162194-27.561607-7.67796-6.06371-14.350194-10.751884-19.631237-15.586807-26.287817-29.101504-35.464584-34.570387-70.440002-111.862636v0.003913z m288.36767 186.413594c-7.476424 8.356924-20.670227 13.191847-40.019704 13.191847-33.427694 0-63.808858-9.229597-107.79277-31.660824-75.648648 8.356924-156.097 17.214754-201.399704 31.729308-2.199293 0.876587-4.832967 1.759043-7.916674 3.077836-54.536215 93.237125-95.031389 132.767663-130.621199 131.19646-11.286054-0.49895-27.694661-7.044-32.973748-10.11988l-6.52157-6.196764-2.29517-4.353583c-3.07588-7.91863-3.954423-15.395054-2.197337-23.751977 4.838837-23.309771 29.907651-60.251638 82.686779-93.237126 8.356924-6.159587 27.430511-15.897917 45.020944-24.25484 13.311204-21.177004 19.45905-34.744531 36.341171-72.259702 19.102937-45.324228 36.505531-99.492589 47.500041-138.191543v-0.44025c-16.267727-53.219378-25.945401-89.310095-9.67376-147.80856 3.958337-16.71189 18.46702-33.864031 34.748444-33.864031h10.552304c10.115967 0 19.791684 3.520043 26.829814 10.552304 29.029107 29.031064 15.39114 103.824649 0.8805 162.323113-0.8805 2.63563-1.322707 4.832967-1.761 6.153717 17.59239 49.697378 45.400538 98.774492 73.108895 121.647926 11.436717 8.791304 22.638634 18.899444 36.71098 26.814161 19.791684-2.20125 37.517128-4.11487 55.547812-4.11487 54.540128 0 87.525615 9.67963 100.279169 30.351814 4.400543 7.034217 6.595923 15.389184 5.281043 24.1844-0.44025 10.996467-4.39663 21.112434-12.31526 29.031064z m-27.796407-36.748157c-4.394673-4.398587-17.024957-16.936907-78.601259-16.936907-3.073923 0-10.622744-0.784623-14.57521 3.612007 32.104987 14.072347 62.830525 24.757704 83.058545 24.757703 3.083707 0 5.72325-0.442207 8.356923-0.876586h1.759044c2.20125-0.8805 3.520043-1.324663 3.960293-5.71738-0.87463-1.324663-1.757087-3.083707-3.958336-4.838837z m-387.124553 63.041845c-9.237424 5.27713-16.71189 10.112054-21.112433 13.634053-31.226444 28.586901-51.018128 57.616008-53.217422 74.331812 19.789727-6.59788 45.737084-35.626987 74.329855-87.961952v-0.003913z m125.574957-297.822284l2.197336-1.761c3.079793-14.072347 5.232127-29.189554 7.87167-38.869184l1.318794-7.036174c4.39663-25.070771 2.71781-39.720334-4.76057-50.272637l-6.59788-2.20125a57.381208 57.381208 0 0 0-3.079794 5.27713c-7.474467 18.47289-7.063567 55.283661 3.0524 94.865072l-0.001956-0.001957z" fill="currentColor"></path></svg>`,
md: `<svg class="chatappico md" viewBox="0 0 1024 1024" width="200" height="200"><path d="M128 128h768a42.666667 42.666667 0 0 1 42.666667 42.666667v682.666666a42.666667 42.666667 0 0 1-42.666667 42.666667H128a42.666667 42.666667 0 0 1-42.666667-42.666667V170.666667a42.666667 42.666667 0 0 1 42.666667-42.666667z m170.666667 533.333333v-170.666666l85.333333 85.333333 85.333333-85.333333v170.666666h85.333334v-298.666666h-85.333334l-85.333333 85.333333-85.333333-85.333333H213.333333v298.666666h85.333334z m469.333333-128v-170.666666h-85.333333v170.666666h-85.333334l128 128 128-128h-85.333333z" p-id="1381" fill="currentColor"></path></svg>`,
copy: `<svg class="chatappico copy" stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="h-4 w-4" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"></path><rect x="8" y="2" width="8" height="4" rx="1" ry="1"></rect></svg>`,
cpok: `<svg class="chatappico cpok" viewBox="0 0 24 24"><g fill="none" stroke="#10a37f" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><rect width="8" height="4" x="8" y="2" rx="1" ry="1"/><path d="M8 4H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-2M16 4h2a2 2 0 0 1 2 2v4m1 4H11"/><path d="m15 10l-4 4l4 4"/></g></svg>`
}[type];
}
function copyBtns() {
Array.from(document.querySelectorAll("main >div>div>div>div>div"))
.forEach(i => {
if (i.querySelector('.chat-item-copy')) return;
if (!i.querySelector('button.rounded-md')) return;
const btn = i.querySelector('button.rounded-md').cloneNode(true);
btn.classList.add('chat-item-copy');
btn.title = 'Copy to clipboard';
btn.innerHTML = setIcon('copy');
i.querySelector('.self-end').appendChild(btn);
btn.onclick = () => {
copyToClipboard(i?.innerText?.trim() || '', btn);
}
})
}
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);
}
if (
document.readyState === "complete" ||
document.readyState === "interactive"

View File

@@ -7,7 +7,7 @@
},
"package": {
"productName": "ChatGPT",
"version": "0.11.1"
"version": "0.12.0"
},
"tauri": {
"allowlist": {

View File

@@ -1,5 +1,5 @@
import { useState } from 'react';
import { Form, Radio, Switch, Input, Tooltip } from 'antd';
import { Form, Radio, Switch, Input, Tooltip, Select, Tag } from 'antd';
import { QuestionCircleOutlined } from '@ant-design/icons';
import { platform } from '@tauri-apps/api/os';
@@ -8,9 +8,16 @@ import { DISABLE_AUTO_COMPLETE } from '@/utils';
export default function General() {
const [platformInfo, setPlatform] = useState('');
const [vlist, setVoices] = useState<any[]>([]);
useInit(async () => {
setPlatform(await platform());
speechSynthesis.addEventListener('voiceschanged', () => {
const voices = speechSynthesis.getVoices();
console.log(voices);
setVoices(voices);
});
setVoices(speechSynthesis.getVoices());
});
return (
@@ -48,6 +55,18 @@ export default function General() {
<Form.Item label={<GlobalShortcutLabel />} name="global_shortcut">
<Input placeholder="CmdOrCtrl+Shift+O" {...DISABLE_AUTO_COMPLETE} />
</Form.Item>
<Form.Item label="Set Speech Language" name="speech_lang">
<Select>
{vlist.map((voice: any) => {
return (
<Select.Option key={voice.voiceURI} value={voice.voiceURI}>
{voice.name} {': '}
<Tag>{voice.lang}</Tag>
</Select.Option>
);
})}
</Select>
</Form.Item>
</>
);
}