Compare commits
53 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
92dc316c4f | ||
|
|
36e7be2c29 | ||
|
|
f1f58f37c4 | ||
|
|
5e295aeb1d | ||
|
|
325dbb305c | ||
|
|
55e17e1e5f | ||
|
|
ae21f2f6a8 | ||
|
|
474670b13f | ||
|
|
0e7fb47c4f | ||
|
|
35353f3564 | ||
|
|
4d363c3847 | ||
|
|
780a23b08f | ||
|
|
78f8daab86 | ||
|
|
3eac43541e | ||
|
|
050b7010fc | ||
|
|
087160861c | ||
|
|
690736b5b0 | ||
|
|
abc8d28c01 | ||
|
|
3fa221b811 | ||
|
|
0cd76ba2fb | ||
|
|
a3791f5c86 | ||
|
|
b6deb67845 | ||
|
|
9478211397 | ||
|
|
8339dc281c | ||
|
|
17ee85eeff | ||
|
|
8cdc944d70 | ||
|
|
423664caa6 | ||
|
|
7dc3d88871 | ||
|
|
a805468a73 | ||
|
|
99729dd45d | ||
|
|
3ca5315c7e | ||
|
|
6901f88b41 | ||
|
|
078413f9a0 | ||
|
|
1b6d11eede | ||
|
|
955a7ab227 | ||
|
|
7f480b4943 | ||
|
|
b4d764abbe | ||
|
|
aad5faa269 | ||
|
|
3839b50891 | ||
|
|
fcff9c4fd1 | ||
|
|
20278ef5c8 | ||
|
|
43896aeb94 | ||
|
|
3402e35177 | ||
|
|
46ac5df90a | ||
|
|
76755efa2d | ||
|
|
1b47208d08 | ||
|
|
4438352acc | ||
|
|
fec14cd6a4 | ||
|
|
50e46f4064 | ||
|
|
dd51ffbf1d | ||
|
|
7a682d0177 | ||
|
|
4239775127 | ||
|
|
89cea44f8b |
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*.js linguist-vendored
|
||||
19
AWESOME.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# Awesome ChatGPT
|
||||
|
||||
- [Awesome ChatGPT](https://github.com/humanloop/awesome-chatgpt) - Curated list of awesome tools, demos, docs for ChatGPT and GPT-3
|
||||
|
||||
## Extension
|
||||
|
||||
`Browser`
|
||||
|
||||
- [ChatGPT Export and Share](https://github.com/liady/ChatGPT-pdf) - A Chrome extension for downloading your ChatGPT history to PNG, PDF or creating a sharable link
|
||||
- [ChatGPT for Google](https://github.com/wong2/chat-gpt-google-extension) - A browser extension to display ChatGPT response alongside Google Search results
|
||||
- [ChatGPT Extension](https://github.com/kazuki-sf/ChatGPT_Extension) - ChatGPT Extension is a really simple Chrome Extension (manifest v3) that you can access OpenAI's ChatGPT from anywhere on the web.
|
||||
|
||||
`VSCode`
|
||||
|
||||
- [ChatGPT Extension for VSCode](https://github.com/mpociot/chatgpt-vscode) - A VSCode extension that allows you to use ChatGPT
|
||||
|
||||
`Bot`
|
||||
|
||||
- [ChatGPT Telegram Bot](https://github.com/altryne/chatGPT-telegram-bot) - This is a very early attempt at having chatGPT work within a telegram bot
|
||||
120
README.md
@@ -5,21 +5,127 @@
|
||||
|
||||
> ChatGPT Desktop Application
|
||||
|
||||
[🚀 Download ChatGPT](https://github.com/lencx/ChatGPT/releases)
|
||||
[](https://github.com/lencx/ChatGPT/releases)
|
||||
[](https://twitter.com/lencx_)
|
||||
|
||||
[Awesome ChatGPT](./AWESOME.md)
|
||||
|
||||
## Downloads
|
||||
|
||||
<!-- download start -->
|
||||
|
||||
**Latest:**
|
||||
|
||||
- `Mac`: [ChatGPT_0.2.0_x64.dmg](https://github.com/lencx/ChatGPT/releases/download/v0.2.0/ChatGPT_0.2.0_x64.dmg)
|
||||
- `Linux`: [chat-gpt_0.2.0_amd64.deb](https://github.com/lencx/ChatGPT/releases/download/v0.2.0/chat-gpt_0.2.0_amd64.deb)
|
||||
- `Windows`: [ChatGPT_0.2.0_x64_en-US.msi](https://github.com/lencx/ChatGPT/releases/download/v0.2.0/ChatGPT_0.2.0_x64_en-US.msi)
|
||||
|
||||
[Other version...](https://github.com/lencx/ChatGPT/releases)
|
||||
|
||||
<!-- download end -->
|
||||
|
||||
## Install
|
||||
|
||||
Easily install with _[Homebrew](https://brew.sh) ([Cask](https://docs.brew.sh/Cask-Cookbook)):_
|
||||
|
||||
~~~ sh
|
||||
brew tap lencx/chatgpt https://github.com/lencx/ChatGPT.git
|
||||
brew install --cask chatgpt --no-quarantine
|
||||
~~~
|
||||
|
||||
Also, if you keep a _[Brewfile](https://github.com/Homebrew/homebrew-bundle#usage)_, you can add something like this:
|
||||
|
||||
~~~ rb
|
||||
repo = "lencx/chatgpt"
|
||||
tap repo, "https://github.com/#{repo}.git"
|
||||
cask "popcorn-time", args: { "no-quarantine": true }
|
||||
~~~
|
||||
|
||||
## Features
|
||||
|
||||
- multi-platform: `macOS` `Linux` `Windows`
|
||||
- export ChatGPT history (PNG, PDF and Share Link)
|
||||
- always on top (whether the window should always be on top of other windows)
|
||||
- inject script
|
||||
- auto updater
|
||||
- hotkey
|
||||
- app menu
|
||||
- tray window
|
||||
- shortcut
|
||||
|
||||
### Menu
|
||||
|
||||
- **ChatGPT**
|
||||
- `Restart ChatGPT`: After editing the injection script file, you can restart the application through this menu item to make the script take effect.
|
||||
- **Preferences**
|
||||
- `Theme` - `Light`, `Dark` (Only macOS and Windows are supported).
|
||||
- `Always On Top`: Window is always on top of other windows.
|
||||
- `Titlebar`: Only supports macOS.
|
||||
- `User Agent` ([#17](https://github.com/lencx/ChatGPT/issues/17)): Customize `user agent` to prevent security detection interception.
|
||||
- `Switch Origin` ([#14](https://github.com/lencx/ChatGPT/issues/14)): Modify website address, the default is `https://chat.openai.com`. Please ensure that the mirror address is consistent with the UI of the original URL, otherwise the export function will fail.
|
||||
- `Clear Config`: Clear all chatgpt configuration files (`path: ~/.chatgpt/*`), dangerous operation, please backup data.
|
||||
- `Inject Script`: User scripts that can modify web pages.
|
||||
- `Awesome ChatGPT`: Related resources recommended.
|
||||
- **Edit** - `Undo`, `Redo`, `Cut`, `Copy`, `SelectAll`, ...
|
||||
- **View** - `Go Back`, `Go Forward`, `Scroll to Top of Screen`, `Scroll to Bottom of Screen`, `Refresh the Screen`, ...
|
||||
- **Help**
|
||||
- `Report Bug`: Defects and Suggestions Feedback.
|
||||
- `Toggle Developer Tools`: Developer tools for debugging web pages.
|
||||
|
||||
## Preview
|
||||
|
||||
<img width="600" src="./assets/install.png" alt="install">
|
||||
<img width="600" src="./assets/chat.png" alt="chat">
|
||||
<img width="320" src="./assets/install.png" alt="install"> <img width="320" src="./assets/chat.png" alt="chat">
|
||||
<img width="320" src="./assets/export.png" alt="export"> <img width="320" src="./assets/tray.png" alt="tray">
|
||||
<img width="320" src="./assets/auto-update.png" alt="auto update">
|
||||
|
||||
## TODO
|
||||
---
|
||||
|
||||
- [ ] export chat history
|
||||
- [ ] ...
|
||||
<a href="https://www.buymeacoffee.com/lencx" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-blue.png" alt="Buy Me A Coffee" style="height: 60px !important;width: 217px !important;" ></a>
|
||||
|
||||
## FAQ
|
||||
|
||||
### Can't open ChatGPT
|
||||
|
||||
If the application cannot be opened after the upgrade, please try to clear the configuration file, which is in the `~/.chatgpt/*` directory.
|
||||
|
||||
### Is it safe?
|
||||
|
||||
It's safe, just a wrapper for [OpenAI ChatGPT](https://chat.openai.com) website, no other data transfer exists (you can check the source code).
|
||||
|
||||
### Developer cannot be verified?
|
||||
|
||||
- [Open a Mac app from an unidentified developer](https://support.apple.com/en-sg/guide/mac-help/mh40616/mac)
|
||||
|
||||
### How do i build it?
|
||||
|
||||
#### PreInstall
|
||||
|
||||
- [Rust](https://www.rust-lang.org/)
|
||||
- [VS Code](https://code.visualstudio.com/)
|
||||
- [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer)
|
||||
- [tauri](https://marketplace.visualstudio.com/items?itemName=tauri-apps.tauri-vscode)
|
||||
|
||||
#### Start
|
||||
|
||||
```bash
|
||||
# step1:
|
||||
git clone https://github.com/lencx/ChatGPT.git
|
||||
|
||||
# step2:
|
||||
cd ChatGPT
|
||||
|
||||
# step3: install deps
|
||||
yarn
|
||||
|
||||
# step4:
|
||||
yarn dev
|
||||
|
||||
# step5:
|
||||
# bundle path: src-tauri/target/release/bundle
|
||||
yarn build
|
||||
```
|
||||
|
||||
## Related
|
||||
|
||||
- [Tauri](https://tauri.app) - Build an optimized, secure, and frontend-independent application for multi-platform deployment.
|
||||
- [ChatGPT](https://openai.com/blog/chatgpt) - ChatGPT: Optimizing Language Models for Dialogue.
|
||||
- [ChatGPT Export and Share](https://github.com/liady/ChatGPT-pdf) - A Chrome extension for downloading your ChatGPT history to PNG, PDF or creating a sharable link.
|
||||
|
||||
@@ -1,8 +1,48 @@
|
||||
# UPDATE LOG
|
||||
|
||||
## v0.2.0
|
||||
|
||||
feat: menu enhancement
|
||||
- customize user-agent to prevent security detection interception
|
||||
- clear all chatgpt configuration files
|
||||
|
||||
## v0.1.8
|
||||
|
||||
feat:
|
||||
- menu enhancement: theme, titlebar
|
||||
- modify website address
|
||||
|
||||
## v0.1.7
|
||||
|
||||
feat: tray window
|
||||
|
||||
## v0.1.6
|
||||
|
||||
feat:
|
||||
- always on top
|
||||
- export ChatGPT history
|
||||
|
||||
## v0.1.5
|
||||
|
||||
fix: mac can't use shortcut keys
|
||||
|
||||
## v0.1.4
|
||||
|
||||
feat:
|
||||
- beautify icons
|
||||
- add system tray menu
|
||||
|
||||
## v0.1.3
|
||||
|
||||
fix: only mac supports `TitleBarStyle`
|
||||
|
||||
## v0.1.2
|
||||
|
||||
initialization
|
||||
|
||||
## v0.1.1
|
||||
|
||||
- initialization
|
||||
initialization
|
||||
|
||||
## v0.1.0
|
||||
|
||||
|
||||
BIN
assets/auto-update.png
Normal file
|
After Width: | Height: | Size: 230 KiB |
BIN
assets/chat.png
|
Before Width: | Height: | Size: 754 KiB After Width: | Height: | Size: 653 KiB |
BIN
assets/export.png
Normal file
|
After Width: | Height: | Size: 226 KiB |
|
Before Width: | Height: | Size: 218 KiB After Width: | Height: | Size: 192 KiB |
BIN
assets/tray.png
Normal file
|
After Width: | Height: | Size: 543 KiB |
22
casks/chatgpt.rb
Normal file
@@ -0,0 +1,22 @@
|
||||
cask "chatgpt" do
|
||||
version "0.1.7"
|
||||
sha256 "1320b30a67e2506f9b45ffd2a48243d6141171c231dd698994ae5156a637eb3f"
|
||||
|
||||
url "https://github.com/lencx/ChatGPT/releases/download/v#{version}/ChatGPT_#{version}_x64.dmg"
|
||||
name "ChatGPT"
|
||||
desc "Desktop wrapper for OpenAI ChatGPT"
|
||||
homepage "https://github.com/lencx/ChatGPT#readme"
|
||||
|
||||
app "ChatGPT.app"
|
||||
|
||||
uninstall quit: "com.lencx.chatgpt"
|
||||
|
||||
zap trash: [
|
||||
"~/.chatgpt",
|
||||
"~/Library/Caches/com.lencx.chatgpt",
|
||||
"~/Library/HTTPStorages/com.lencx.chatgpt.binarycookies",
|
||||
"~/Library/Preferences/com.lencx.chatgpt.plist",
|
||||
"~/Library/Saved Application State/com.lencx.chatgpt.savedState",
|
||||
"~/Library/WebKit/com.lencx.chatgpt",
|
||||
]
|
||||
end
|
||||
BIN
logo.png
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 22 KiB |
11
package.json
@@ -6,12 +6,21 @@
|
||||
"build": "yarn tauri build",
|
||||
"updater": "tr updater",
|
||||
"release": "tr release --git",
|
||||
"download": "node ./scripts/download.js",
|
||||
"tr": "tr",
|
||||
"tauri": "tauri"
|
||||
},
|
||||
"license": "MIT",
|
||||
"author": "lencx <cxin1314@gmail.com>",
|
||||
"keywords": ["chatgpt", "app", "desktop", "tauri", "macos", "linux", "windows"],
|
||||
"keywords": [
|
||||
"chatgpt",
|
||||
"app",
|
||||
"desktop",
|
||||
"tauri",
|
||||
"macos",
|
||||
"linux",
|
||||
"windows"
|
||||
],
|
||||
"homepage": "https://github.com/lencx/ChatGPT",
|
||||
"bugs": "https://github.com/lencx/ChatGPT/issues",
|
||||
"repository": {
|
||||
|
||||
26
scripts/download.js
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
const fs = require('fs');
|
||||
|
||||
const argv = process.argv.slice(2);
|
||||
|
||||
async function init() {
|
||||
const content = fs.readFileSync('README.md', 'utf8').split('\n');
|
||||
const startRe = /<!-- download start -->/;
|
||||
const endRe = /<!-- download end -->/;
|
||||
|
||||
let flag = false;
|
||||
for (let i = 0; i < content.length; i++) {
|
||||
if (startRe.test(content[i])) {
|
||||
flag = true;
|
||||
}
|
||||
if (flag) {
|
||||
content[i] = content[i].replace(/(\d+).(\d+).(\d+)/g, argv[0]);
|
||||
}
|
||||
if (endRe.test(content[i])) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
fs.writeFileSync('README.md', content.join('\n'), 'utf8');
|
||||
}
|
||||
|
||||
init().catch(console.error);
|
||||
@@ -14,10 +14,11 @@ rust-version = "1.57"
|
||||
tauri-build = {version = "1.2.1", features = [] }
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.66"
|
||||
serde_json = "1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
tauri = { version = "1.2.1", features = ["api-all", "devtools", "system-tray", "updater"] }
|
||||
anyhow = "1.0.66"
|
||||
tauri = { version = "1.2.2", features = ["api-all", "devtools", "system-tray", "updater"] }
|
||||
tauri-plugin-positioner = { version = "1.0.4", features = ["system-tray"] }
|
||||
|
||||
[features]
|
||||
# by default Tauri runs in production mode
|
||||
@@ -26,3 +27,10 @@ default = [ "custom-protocol" ]
|
||||
# this feature is used used for production builds where `devPath` points to the filesystem
|
||||
# DO NOT remove this
|
||||
custom-protocol = [ "tauri/custom-protocol" ]
|
||||
|
||||
# fix: mac v1.2.0 can not copy/paste
|
||||
# https://github.com/tauri-apps/tauri/issues/5669
|
||||
[profile.release]
|
||||
strip = true
|
||||
lto = true
|
||||
opt-level = "z"
|
||||
|
||||
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 6.4 KiB |
|
Before Width: | Height: | Size: 7.7 KiB After Width: | Height: | Size: 8.5 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 104 KiB |
BIN
src-tauri/icons/tray-icon.png
Normal file
|
After Width: | Height: | Size: 76 KiB |
@@ -1,12 +1,14 @@
|
||||
use tauri::Manager;
|
||||
use crate::{conf::ChatConfJson, utils};
|
||||
use std::fs;
|
||||
use tauri::{api, command, AppHandle, Manager};
|
||||
|
||||
#[tauri::command]
|
||||
pub fn drag_window(app: tauri::AppHandle) {
|
||||
#[command]
|
||||
pub fn drag_window(app: AppHandle) {
|
||||
app.get_window("core").unwrap().start_dragging().unwrap();
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn fullscreen(app: tauri::AppHandle) {
|
||||
#[command]
|
||||
pub fn fullscreen(app: AppHandle) {
|
||||
let win = app.get_window("core").unwrap();
|
||||
if win.is_fullscreen().unwrap() {
|
||||
win.set_fullscreen(false).unwrap();
|
||||
@@ -14,3 +16,47 @@ pub fn fullscreen(app: tauri::AppHandle) {
|
||||
win.set_fullscreen(true).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[command]
|
||||
pub fn download(_app: AppHandle, name: String, blob: Vec<u8>) {
|
||||
let path = api::path::download_dir().unwrap().join(name);
|
||||
fs::write(&path, blob).unwrap();
|
||||
utils::open_file(path);
|
||||
}
|
||||
|
||||
#[command]
|
||||
pub fn open_link(app: AppHandle, url: String) {
|
||||
api::shell::open(&app.shell_scope(), url, None).unwrap();
|
||||
}
|
||||
|
||||
#[command]
|
||||
pub fn get_chat_conf() -> ChatConfJson {
|
||||
ChatConfJson::get_chat_conf()
|
||||
}
|
||||
|
||||
#[command]
|
||||
pub fn form_confirm(app: AppHandle, data: serde_json::Value) {
|
||||
ChatConfJson::amend(&serde_json::json!(data)).unwrap();
|
||||
tauri::api::process::restart(&app.env());
|
||||
}
|
||||
|
||||
#[command]
|
||||
pub fn form_cancel(app: AppHandle, label: &str, title: &str, msg: &str) {
|
||||
let win = app.app_handle().get_window(label).unwrap();
|
||||
tauri::api::dialog::ask(
|
||||
app.app_handle().get_window(label).as_ref(),
|
||||
title,
|
||||
msg,
|
||||
move |is_cancel| {
|
||||
if is_cancel {
|
||||
win.close().unwrap();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[command]
|
||||
pub fn form_msg(app: AppHandle, label: &str, title: &str, msg: &str) {
|
||||
let win = app.app_handle().get_window(label);
|
||||
tauri::api::dialog::message(win.as_ref(), title, msg);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
use crate::utils;
|
||||
use crate::{
|
||||
app::window,
|
||||
conf::{self, ChatConfJson},
|
||||
utils,
|
||||
};
|
||||
use tauri::{
|
||||
utils::assets::EmbeddedAssets, AboutMetadata, AppHandle, Context, CustomMenuItem, Manager,
|
||||
Menu, MenuItem, Submenu, SystemTrayEvent, WindowMenuEvent,
|
||||
Menu, MenuItem, Submenu, SystemTray, SystemTrayEvent, SystemTrayMenu, WindowMenuEvent,
|
||||
};
|
||||
use tauri_plugin_positioner::{on_tray_event, Position, WindowExt};
|
||||
|
||||
// --- Menu
|
||||
pub fn init(context: &Context<EmbeddedAssets>) -> Menu {
|
||||
let chat_conf = ChatConfJson::get_chat_conf();
|
||||
let name = &context.package_info().name;
|
||||
let app_menu = Submenu::new(
|
||||
name,
|
||||
@@ -13,9 +19,10 @@ pub fn init(context: &Context<EmbeddedAssets>) -> Menu {
|
||||
.add_native_item(MenuItem::About(name.into(), AboutMetadata::default()))
|
||||
.add_native_item(MenuItem::Separator)
|
||||
.add_item(
|
||||
CustomMenuItem::new("inject_script".to_string(), "Inject Script")
|
||||
.accelerator("CmdOrCtrl+J"),
|
||||
CustomMenuItem::new("restart".to_string(), "Restart ChatGPT")
|
||||
.accelerator("CmdOrCtrl+Shift+R"),
|
||||
)
|
||||
.add_native_item(MenuItem::Services)
|
||||
.add_native_item(MenuItem::Separator)
|
||||
.add_native_item(MenuItem::Hide)
|
||||
.add_native_item(MenuItem::HideOthers)
|
||||
@@ -24,6 +31,67 @@ pub fn init(context: &Context<EmbeddedAssets>) -> Menu {
|
||||
.add_native_item(MenuItem::Quit),
|
||||
);
|
||||
|
||||
let always_on_top = CustomMenuItem::new("always_on_top".to_string(), "Always On Top")
|
||||
.accelerator("CmdOrCtrl+T");
|
||||
let titlebar =
|
||||
CustomMenuItem::new("titlebar".to_string(), "Titlebar").accelerator("CmdOrCtrl+B");
|
||||
let theme_light = CustomMenuItem::new("theme_light".to_string(), "Light");
|
||||
let theme_dark = CustomMenuItem::new("theme_dark".to_string(), "Dark");
|
||||
let is_dark = chat_conf.theme == "Dark";
|
||||
|
||||
let always_on_top_menu = if chat_conf.always_on_top {
|
||||
always_on_top.selected()
|
||||
} else {
|
||||
always_on_top
|
||||
};
|
||||
let titlebar_menu = if chat_conf.titlebar {
|
||||
titlebar.selected()
|
||||
} else {
|
||||
titlebar
|
||||
};
|
||||
|
||||
let preferences_menu = Submenu::new(
|
||||
"Preferences",
|
||||
Menu::with_items([
|
||||
Submenu::new(
|
||||
"Theme",
|
||||
Menu::new()
|
||||
.add_item(if is_dark {
|
||||
theme_light
|
||||
} else {
|
||||
theme_light.selected()
|
||||
})
|
||||
.add_item(if is_dark {
|
||||
theme_dark.selected()
|
||||
} else {
|
||||
theme_dark
|
||||
}),
|
||||
)
|
||||
.into(),
|
||||
always_on_top_menu.into(),
|
||||
#[cfg(target_os = "macos")]
|
||||
titlebar_menu.into(),
|
||||
// fix: Checking if the site connection is secure
|
||||
// @link: https://github.com/lencx/ChatGPT/issues/17
|
||||
CustomMenuItem::new("user_agent".to_string(), "User Agent")
|
||||
.accelerator("CmdOrCtrl+U")
|
||||
.into(),
|
||||
CustomMenuItem::new("switch_origin".to_string(), "Switch Origin")
|
||||
.accelerator("CmdOrCtrl+O")
|
||||
.into(),
|
||||
CustomMenuItem::new("inject_script".to_string(), "Inject Script")
|
||||
.accelerator("CmdOrCtrl+J")
|
||||
.into(),
|
||||
CustomMenuItem::new("clear_conf".to_string(), "Clear Config")
|
||||
.accelerator("CmdOrCtrl+D")
|
||||
.into(),
|
||||
MenuItem::Separator.into(),
|
||||
CustomMenuItem::new("awesome".to_string(), "Awesome ChatGPT")
|
||||
.accelerator("CmdOrCtrl+Z")
|
||||
.into(),
|
||||
]),
|
||||
);
|
||||
|
||||
let edit_menu = Submenu::new(
|
||||
"Edit",
|
||||
Menu::new()
|
||||
@@ -54,6 +122,7 @@ pub fn init(context: &Context<EmbeddedAssets>) -> Menu {
|
||||
CustomMenuItem::new("scroll_bottom".to_string(), "Scroll to Bottom of Screen")
|
||||
.accelerator("CmdOrCtrl+Down"),
|
||||
)
|
||||
.add_native_item(MenuItem::Zoom)
|
||||
.add_native_item(MenuItem::Separator)
|
||||
.add_item(
|
||||
CustomMenuItem::new("reload".to_string(), "Refresh the Screen")
|
||||
@@ -73,6 +142,7 @@ pub fn init(context: &Context<EmbeddedAssets>) -> Menu {
|
||||
|
||||
Menu::new()
|
||||
.add_submenu(app_menu)
|
||||
.add_submenu(preferences_menu)
|
||||
.add_submenu(edit_menu)
|
||||
.add_submenu(view_menu)
|
||||
.add_submenu(help_menu)
|
||||
@@ -82,56 +152,69 @@ pub fn init(context: &Context<EmbeddedAssets>) -> Menu {
|
||||
pub fn menu_handler(event: WindowMenuEvent<tauri::Wry>) {
|
||||
let win = Some(event.window()).unwrap();
|
||||
let app = win.app_handle();
|
||||
let state: tauri::State<conf::ChatState> = app.state();
|
||||
let script_path = utils::script_path().to_string_lossy().to_string();
|
||||
let menu_id = event.menu_item_id();
|
||||
|
||||
match event.menu_item_id() {
|
||||
let core_window = app.get_window("core").unwrap();
|
||||
let menu_handle = core_window.menu_handle();
|
||||
|
||||
match menu_id {
|
||||
// App
|
||||
"inject_script" => {
|
||||
tauri::api::shell::open(
|
||||
&app.shell_scope(),
|
||||
utils::script_path().to_string_lossy(),
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
"restart" => tauri::api::process::restart(&app.env()),
|
||||
// Preferences
|
||||
"inject_script" => open(&app, script_path),
|
||||
"clear_conf" => utils::clear_conf(&app),
|
||||
"switch_origin" => window::origin_window(&app),
|
||||
"user_agent" => window::ua_window(&app),
|
||||
"awesome" => open(&app, conf::AWESOME_URL.to_string()),
|
||||
"titlebar" => {
|
||||
let chat_conf = conf::ChatConfJson::get_chat_conf();
|
||||
ChatConfJson::amend(&serde_json::json!({ "titlebar": !chat_conf.titlebar })).unwrap();
|
||||
tauri::api::process::restart(&app.env());
|
||||
}
|
||||
"theme_light" | "theme_dark" => {
|
||||
let theme = if menu_id == "theme_dark" {
|
||||
"Dark"
|
||||
} else {
|
||||
"Light"
|
||||
};
|
||||
ChatConfJson::amend(&serde_json::json!({ "theme": theme })).unwrap();
|
||||
tauri::api::process::restart(&app.env());
|
||||
}
|
||||
"always_on_top" => {
|
||||
let mut always_on_top = state.always_on_top.lock().unwrap();
|
||||
*always_on_top = !*always_on_top;
|
||||
menu_handle
|
||||
.get_item(menu_id)
|
||||
.set_selected(*always_on_top)
|
||||
.unwrap();
|
||||
win.set_always_on_top(*always_on_top).unwrap();
|
||||
ChatConfJson::amend(&serde_json::json!({ "always_on_top": *always_on_top })).unwrap();
|
||||
}
|
||||
// View
|
||||
"go_back" => {
|
||||
win.eval("window.history.go(-1)").unwrap();
|
||||
}
|
||||
"go_forward" => {
|
||||
win.eval("window.history.go(1)").unwrap();
|
||||
}
|
||||
"scroll_top" => {
|
||||
win.eval(
|
||||
"reload" => win.eval("window.location.reload()").unwrap(),
|
||||
"go_back" => win.eval("window.history.go(-1)").unwrap(),
|
||||
"go_forward" => win.eval("window.history.go(1)").unwrap(),
|
||||
"scroll_top" => win
|
||||
.eval(
|
||||
r#"window.scroll({
|
||||
top: 0,
|
||||
left: 0,
|
||||
behavior: "smooth"
|
||||
})"#,
|
||||
})"#,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
"scroll_bottom" => {
|
||||
win.eval(
|
||||
.unwrap(),
|
||||
"scroll_bottom" => win
|
||||
.eval(
|
||||
r#"window.scroll({
|
||||
top: document.body.scrollHeight,
|
||||
left: 0,
|
||||
behavior: "smooth",
|
||||
})"#,
|
||||
behavior: "smooth"})"#,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
"reload" => {
|
||||
win.eval("window.location.reload()").unwrap();
|
||||
}
|
||||
.unwrap(),
|
||||
// Help
|
||||
"report_bug" => {
|
||||
tauri::api::shell::open(
|
||||
&app.shell_scope(),
|
||||
"https://github.com/lencx/ChatGPT/issues",
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
"report_bug" => open(&app, conf::ISSUES_URL.to_string()),
|
||||
"dev_tools" => {
|
||||
win.open_devtools();
|
||||
win.close_devtools();
|
||||
@@ -140,20 +223,29 @@ pub fn menu_handler(event: WindowMenuEvent<tauri::Wry>) {
|
||||
}
|
||||
}
|
||||
|
||||
// --- SystemTray Menu
|
||||
pub fn tray_menu() -> SystemTray {
|
||||
SystemTray::new().with_menu(SystemTrayMenu::new())
|
||||
}
|
||||
|
||||
// --- SystemTray Event
|
||||
pub fn tray_handler(app: &AppHandle, event: SystemTrayEvent) {
|
||||
if let SystemTrayEvent::LeftClick {
|
||||
position: _,
|
||||
size: _,
|
||||
..
|
||||
} = event
|
||||
{
|
||||
let win = app.get_window("core").unwrap();
|
||||
if win.is_visible().unwrap() {
|
||||
win.hide().unwrap();
|
||||
pub fn tray_handler(handle: &AppHandle, event: SystemTrayEvent) {
|
||||
let core_win = handle.get_window("core").unwrap();
|
||||
on_tray_event(handle, &event);
|
||||
|
||||
if let SystemTrayEvent::LeftClick { .. } = event {
|
||||
core_win.minimize().unwrap();
|
||||
let mini_win = handle.get_window("mini").unwrap();
|
||||
mini_win.move_window(Position::TrayCenter).unwrap();
|
||||
|
||||
if mini_win.is_visible().unwrap() {
|
||||
mini_win.hide().unwrap();
|
||||
} else {
|
||||
win.show().unwrap();
|
||||
win.set_focus().unwrap();
|
||||
mini_win.show().unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn open(app: &AppHandle, path: String) {
|
||||
tauri::api::shell::open(&app.shell_scope(), path, None).unwrap();
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod cmd;
|
||||
pub mod menu;
|
||||
pub mod setup;
|
||||
pub mod window;
|
||||
|
||||
@@ -1,23 +1,43 @@
|
||||
use crate::utils;
|
||||
use tauri::{
|
||||
utils::{config::WindowUrl, TitleBarStyle},
|
||||
window::WindowBuilder,
|
||||
App,
|
||||
};
|
||||
use crate::{app::window, conf::ChatConfJson, utils};
|
||||
use tauri::{utils::config::WindowUrl, window::WindowBuilder, App, Manager};
|
||||
|
||||
pub fn init(app: &mut App) -> std::result::Result<(), Box<dyn std::error::Error>> {
|
||||
let conf = utils::get_tauri_conf().unwrap();
|
||||
let url = conf.build.dev_path.to_string();
|
||||
let chat_conf = ChatConfJson::get_chat_conf();
|
||||
let url = chat_conf.origin.to_string();
|
||||
let theme = ChatConfJson::theme();
|
||||
window::mini_window(&app.app_handle());
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
WindowBuilder::new(app, "core", WindowUrl::App(url.into()))
|
||||
.resizable(true)
|
||||
.fullscreen(false)
|
||||
.initialization_script(include_str!("../core.js"))
|
||||
.initialization_script(&utils::user_script())
|
||||
.title_bar_style(TitleBarStyle::Overlay)
|
||||
.inner_size(800.0, 600.0)
|
||||
.hidden_title(true)
|
||||
.user_agent("5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36")
|
||||
.theme(theme)
|
||||
.always_on_top(chat_conf.always_on_top)
|
||||
.title_bar_style(ChatConfJson::titlebar())
|
||||
.initialization_script(&utils::user_script())
|
||||
.initialization_script(include_str!("../assets/html2canvas.js"))
|
||||
.initialization_script(include_str!("../assets/jspdf.js"))
|
||||
.initialization_script(include_str!("../assets/core.js"))
|
||||
.initialization_script(include_str!("../assets/export.js"))
|
||||
.user_agent(&chat_conf.ua_pc)
|
||||
.build()?;
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
WindowBuilder::new(app, "core", WindowUrl::App(url.into()))
|
||||
.title("ChatGPT")
|
||||
.resizable(true)
|
||||
.fullscreen(false)
|
||||
.inner_size(800.0, 600.0)
|
||||
.theme(theme)
|
||||
.always_on_top(chat_conf.always_on_top)
|
||||
.initialization_script(&utils::user_script())
|
||||
.initialization_script(include_str!("../assets/html2canvas.js"))
|
||||
.initialization_script(include_str!("../assets/jspdf.js"))
|
||||
.initialization_script(include_str!("../assets/core.js"))
|
||||
.initialization_script(include_str!("../assets/export.js"))
|
||||
.user_agent(&chat_conf.ua_pc)
|
||||
.build()?;
|
||||
|
||||
Ok(())
|
||||
|
||||
53
src-tauri/src/app/window.rs
Normal file
@@ -0,0 +1,53 @@
|
||||
use crate::{conf, utils};
|
||||
use tauri::{utils::config::WindowUrl, window::WindowBuilder};
|
||||
|
||||
pub fn mini_window(handle: &tauri::AppHandle) {
|
||||
let chat_conf = conf::ChatConfJson::get_chat_conf();
|
||||
let theme = conf::ChatConfJson::theme();
|
||||
|
||||
WindowBuilder::new(handle, "mini", WindowUrl::App(chat_conf.origin.into()))
|
||||
.resizable(false)
|
||||
.fullscreen(false)
|
||||
.inner_size(360.0, 540.0)
|
||||
.decorations(false)
|
||||
.always_on_top(true)
|
||||
.theme(theme)
|
||||
.initialization_script(&utils::user_script())
|
||||
.initialization_script(include_str!("../assets/html2canvas.js"))
|
||||
.initialization_script(include_str!("../assets/jspdf.js"))
|
||||
.initialization_script(include_str!("../assets/core.js"))
|
||||
.initialization_script(include_str!("../assets/export.js"))
|
||||
.user_agent(&chat_conf.ua_phone)
|
||||
.build()
|
||||
.unwrap()
|
||||
.hide()
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub fn origin_window(handle: &tauri::AppHandle) {
|
||||
let chat_conf = conf::ChatConfJson::get_chat_conf();
|
||||
WindowBuilder::new(handle, "origin", WindowUrl::App(chat_conf.origin.into()))
|
||||
.resizable(false)
|
||||
.fullscreen(false)
|
||||
.inner_size(400.0, 300.0)
|
||||
.always_on_top(true)
|
||||
.decorations(false)
|
||||
.initialization_script(include_str!("../assets/core.js"))
|
||||
.initialization_script(include_str!("../assets/origin.js"))
|
||||
.build()
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub fn ua_window(handle: &tauri::AppHandle) {
|
||||
let chat_conf = conf::ChatConfJson::get_chat_conf();
|
||||
WindowBuilder::new(handle, "ua", WindowUrl::App(chat_conf.origin.into()))
|
||||
.resizable(false)
|
||||
.fullscreen(false)
|
||||
.inner_size(540.0, 480.0)
|
||||
.always_on_top(true)
|
||||
.decorations(false)
|
||||
.initialization_script(include_str!("../assets/core.js"))
|
||||
.initialization_script(include_str!("../assets/ua.js"))
|
||||
.build()
|
||||
.unwrap();
|
||||
}
|
||||
97
src-tauri/src/assets/core.js
vendored
Normal file
@@ -0,0 +1,97 @@
|
||||
// *** Core Script - IPC ***
|
||||
const uid = () => window.crypto.getRandomValues(new Uint32Array(1))[0];
|
||||
function transformCallback(callback = () => {}, once = false) {
|
||||
const identifier = uid();
|
||||
const prop = `_${identifier}`;
|
||||
Object.defineProperty(window, prop, {
|
||||
value: (result) => {
|
||||
if (once) {
|
||||
Reflect.deleteProperty(window, prop);
|
||||
}
|
||||
return callback(result)
|
||||
},
|
||||
writable: false,
|
||||
configurable: true,
|
||||
})
|
||||
return identifier;
|
||||
}
|
||||
async function invoke(cmd, args) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!window.__TAURI_POST_MESSAGE__) reject('__TAURI_POST_MESSAGE__ does not exist!');
|
||||
const callback = transformCallback((e) => {
|
||||
resolve(e);
|
||||
Reflect.deleteProperty(window, `_${error}`);
|
||||
}, true)
|
||||
const error = transformCallback((e) => {
|
||||
reject(e);
|
||||
Reflect.deleteProperty(window, `_${callback}`);
|
||||
}, true)
|
||||
window.__TAURI_POST_MESSAGE__({
|
||||
cmd,
|
||||
callback,
|
||||
error,
|
||||
...args
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
window.uid = uid;
|
||||
window.invoke = invoke;
|
||||
window.transformCallback = transformCallback;
|
||||
|
||||
async function init() {
|
||||
if (__TAURI_METADATA__.__currentWindow.label === 'mini') {
|
||||
document.getElementsByTagName('html')[0].style['font-size'] = '70%';
|
||||
}
|
||||
|
||||
if (__TAURI_METADATA__.__currentWindow.label !== 'core') return;
|
||||
|
||||
async function platform() {
|
||||
return invoke('platform', {
|
||||
__tauriModule: 'Os',
|
||||
message: { cmd: 'platform' }
|
||||
});
|
||||
}
|
||||
|
||||
const _platform = await platform();
|
||||
const chatConf = await invoke('get_chat_conf') || {};
|
||||
if (/darwin/.test(_platform) && !chatConf.titlebar) {
|
||||
const topStyleDom = document.createElement("style");
|
||||
topStyleDom.innerHTML = `#chatgpt-app-window-top{position:fixed;top:0;z-index:999999999;width:100%;height:24px;background:transparent;cursor:grab;cursor:-webkit-grab;user-select:none;-webkit-user-select:none;}#chatgpt-app-window-top:active {cursor:grabbing;cursor:-webkit-grabbing;}`;
|
||||
document.head.appendChild(topStyleDom);
|
||||
const topDom = document.createElement("div");
|
||||
topDom.id = "chatgpt-app-window-top";
|
||||
document.body.appendChild(topDom);
|
||||
|
||||
topDom.addEventListener("mousedown", () => invoke("drag_window"));
|
||||
topDom.addEventListener("touchstart", () => invoke("drag_window"));
|
||||
topDom.addEventListener("dblclick", () => invoke("fullscreen"));
|
||||
}
|
||||
|
||||
document.addEventListener("click", (e) => {
|
||||
const origin = e.target.closest("a");
|
||||
if (origin && origin.href && origin.target !== '_self') {
|
||||
invoke('open_link', { url: origin.href });
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('wheel', function(event) {
|
||||
const deltaX = event.wheelDeltaX;
|
||||
if (Math.abs(deltaX) >= 50) {
|
||||
if (deltaX > 0) {
|
||||
window.history.go(-1);
|
||||
} else {
|
||||
window.history.go(1);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
document.readyState === "complete" ||
|
||||
document.readyState === "interactive"
|
||||
) {
|
||||
init();
|
||||
} else {
|
||||
document.addEventListener("DOMContentLoaded", init);
|
||||
}
|
||||
243
src-tauri/src/assets/export.js
vendored
Normal file
@@ -0,0 +1,243 @@
|
||||
// *** Core Script - Export ***
|
||||
// @ref: https://github.com/liady/ChatGPT-pdf/blob/main/src/content_script.js
|
||||
|
||||
async function init() {
|
||||
const chatConf = await invoke('get_chat_conf') || {};
|
||||
if (window.buttonsInterval) {
|
||||
clearInterval(window.buttonsInterval);
|
||||
}
|
||||
window.buttonsInterval = setInterval(() => {
|
||||
const actionsArea = document.querySelector("form>div>div");
|
||||
if (!actionsArea) {
|
||||
return;
|
||||
}
|
||||
const buttons = actionsArea.querySelectorAll("button");
|
||||
const hasTryAgainButton = Array.from(buttons).some((button) => {
|
||||
return !button.id?.includes("download");
|
||||
});
|
||||
if (hasTryAgainButton && buttons.length === 1) {
|
||||
const TryAgainButton = actionsArea.querySelector("button");
|
||||
addActionsButtons(actionsArea, TryAgainButton, chatConf);
|
||||
} else if (!hasTryAgainButton) {
|
||||
removeButtons();
|
||||
}
|
||||
}, 200);
|
||||
}
|
||||
|
||||
const Format = {
|
||||
PNG: "png",
|
||||
PDF: "pdf",
|
||||
};
|
||||
|
||||
function addActionsButtons(actionsArea, TryAgainButton, chatConf) {
|
||||
const downloadButton = TryAgainButton.cloneNode(true);
|
||||
downloadButton.id = "download-png-button";
|
||||
downloadButton.innerText = "Generate PNG";
|
||||
downloadButton.onclick = () => {
|
||||
downloadThread();
|
||||
};
|
||||
actionsArea.appendChild(downloadButton);
|
||||
|
||||
const downloadPdfButton = TryAgainButton.cloneNode(true);
|
||||
downloadPdfButton.id = "download-pdf-button";
|
||||
downloadPdfButton.innerText = "Download PDF";
|
||||
downloadPdfButton.onclick = () => {
|
||||
downloadThread({ as: Format.PDF });
|
||||
};
|
||||
actionsArea.appendChild(downloadPdfButton);
|
||||
|
||||
if (new RegExp('//chat.openai.com').test(chatConf.origin)) {
|
||||
const exportHtml = TryAgainButton.cloneNode(true);
|
||||
exportHtml.id = "download-html-button";
|
||||
exportHtml.innerText = "Share Link";
|
||||
exportHtml.onclick = () => {
|
||||
sendRequest();
|
||||
};
|
||||
actionsArea.appendChild(exportHtml);
|
||||
}
|
||||
}
|
||||
|
||||
function removeButtons() {
|
||||
const downloadButton = document.getElementById("download-png-button");
|
||||
const downloadPdfButton = document.getElementById("download-pdf-button");
|
||||
const downloadHtmlButton = document.getElementById("download-html-button");
|
||||
if (downloadButton) {
|
||||
downloadButton.remove();
|
||||
}
|
||||
if (downloadPdfButton) {
|
||||
downloadPdfButton.remove();
|
||||
}
|
||||
if (downloadHtmlButton) {
|
||||
downloadHtmlButton.remove();
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
onclone: function (cloneDoc) {
|
||||
//Make small fix of position to all the text containers
|
||||
let listOfTexts = cloneDoc.getElementsByClassName("min-h-[20px]");
|
||||
Array.from(listOfTexts).forEach((text) => {
|
||||
text.style.position = "relative";
|
||||
text.style.top = "-8px";
|
||||
});
|
||||
|
||||
//Delete copy button from code blocks
|
||||
let listOfCopyBtns = cloneDoc.querySelectorAll("button.flex");
|
||||
Array.from(listOfCopyBtns).forEach(
|
||||
(btn) => (btn.style.visibility = "hidden")
|
||||
);
|
||||
},
|
||||
}).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);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
invoke('download', { name: `chatgpt-${Date.now()}.png`, blob: Array.from(new Uint8Array(data)) });
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
const data = pdf.__private__.getArrayBuffer(pdf.__private__.buildDocument());
|
||||
invoke('download', { name: `chatgpt-${Date.now()}.pdf`, blob: Array.from(new Uint8Array(data)) });
|
||||
}
|
||||
|
||||
class Elements {
|
||||
constructor() {
|
||||
this.init();
|
||||
}
|
||||
init() {
|
||||
// this.threadWrapper = document.querySelector(".cdfdFe");
|
||||
this.spacer = document.querySelector(".w-full.h-48.flex-shrink-0");
|
||||
this.thread = document.querySelector(
|
||||
"[class*='react-scroll-to-bottom']>[class*='react-scroll-to-bottom']>div"
|
||||
);
|
||||
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];
|
||||
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", "");
|
||||
});
|
||||
}
|
||||
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", "");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function selectElementByClassPrefix(classPrefix) {
|
||||
const element = document.querySelector(`[class^='${classPrefix}']`);
|
||||
return element;
|
||||
}
|
||||
|
||||
async function sendRequest() {
|
||||
const data = getData();
|
||||
const uploadUrlResponse = await fetch(
|
||||
"https://chatgpt-static.s3.amazonaws.com/url.txt"
|
||||
);
|
||||
const uploadUrl = await uploadUrlResponse.text();
|
||||
fetch(uploadUrl, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
invoke('open_link', { url: data.url });
|
||||
});
|
||||
}
|
||||
|
||||
function getData() {
|
||||
const globalCss = getCssFromSheet(
|
||||
document.querySelector("link[rel=stylesheet]").sheet
|
||||
);
|
||||
const localCss =
|
||||
getCssFromSheet(
|
||||
document.querySelector(`style[data-styled][data-styled-version]`).sheet
|
||||
) || "body{}";
|
||||
const data = {
|
||||
main: document.querySelector("main").outerHTML,
|
||||
// css: `${globalCss} /* GLOBAL-LOCAL */ ${localCss}`,
|
||||
globalCss,
|
||||
localCss,
|
||||
};
|
||||
return data;
|
||||
}
|
||||
|
||||
function getCssFromSheet(sheet) {
|
||||
return Array.from(sheet.cssRules)
|
||||
.map((rule) => rule.cssText)
|
||||
.join("");
|
||||
}
|
||||
|
||||
// run init
|
||||
if (
|
||||
document.readyState === "complete" ||
|
||||
document.readyState === "interactive"
|
||||
) {
|
||||
init();
|
||||
} else {
|
||||
document.addEventListener("DOMContentLoaded", init);
|
||||
}
|
||||
20
src-tauri/src/assets/html2canvas.js
vendored
Normal file
397
src-tauri/src/assets/jspdf.js
vendored
Normal file
77
src-tauri/src/assets/origin.js
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
function init() {
|
||||
document.body.innerHTML = `<style>
|
||||
body {
|
||||
height: 100vh;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-family: Söhne,ui-sans-serif,system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,sans-serif,Helvetica Neue,Arial,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;
|
||||
}
|
||||
h3 {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
input {
|
||||
all: unset;
|
||||
width: 280px;
|
||||
height: 30px;
|
||||
margin-bottom: 10px;
|
||||
padding: 0 5px;
|
||||
border: solid 2px #d8d8d8;
|
||||
background-color: #fff;
|
||||
border-radius: 5px !important;
|
||||
color: #4a4a4a;
|
||||
}
|
||||
button {
|
||||
all: unset;
|
||||
height: 30px;
|
||||
font-size: 16px;
|
||||
padding: 0 10px;
|
||||
line-height: 30px;
|
||||
margin: 0 5px;
|
||||
color: #fff;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
#cancel {
|
||||
background-color: #999;
|
||||
}
|
||||
#confirm {
|
||||
background-color: #10a37f;
|
||||
}
|
||||
</style>
|
||||
<h3>Switch Origin</h3>
|
||||
<input id="input" type="text" autocapitalize="off" autocomplete="off" spellcheck="false" autofocus placeholder="https://chat.openai.com" />
|
||||
<div class="btns">
|
||||
<button id="cancel">Cancel</button>
|
||||
<button id="confirm">Confirm</button>
|
||||
</div>`;
|
||||
|
||||
const srcipt = document.createElement('script');
|
||||
srcipt.innerHTML = `const input = document.getElementById('input');
|
||||
const cancelBtn = document.getElementById('cancel');
|
||||
const confirmBtn = document.getElementById('confirm');
|
||||
cancelBtn.addEventListener('click', () => {
|
||||
window.invoke('form_cancel', { label: 'origin', title: 'Switch Origin', msg: 'Are you sure you want to cancel editing?' });
|
||||
})
|
||||
confirmBtn.addEventListener('click', () => {
|
||||
if (/^https?:\\/\\//.test(input.value)) {
|
||||
window.invoke('form_confirm', { data: { origin: input.value } });
|
||||
} else {
|
||||
window.invoke('form_msg', { label: 'origin', title: 'Switch Origin', msg: 'Invalid URL!' });
|
||||
}
|
||||
})`;
|
||||
document.head.appendChild(srcipt);
|
||||
}
|
||||
|
||||
// run init
|
||||
if (
|
||||
document.readyState === "complete" ||
|
||||
document.readyState === "interactive"
|
||||
) {
|
||||
init();
|
||||
} else {
|
||||
document.addEventListener("DOMContentLoaded", init);
|
||||
}
|
||||
89
src-tauri/src/assets/ua.js
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
function init() {
|
||||
document.body.innerHTML = `<style>
|
||||
body {
|
||||
height: 100vh;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-family: Söhne,ui-sans-serif,system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,sans-serif,Helvetica Neue,Arial,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;
|
||||
}
|
||||
h3 {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
textarea {
|
||||
all: unset;
|
||||
width: 300px;
|
||||
height: 60px;
|
||||
margin-bottom: 10px;
|
||||
padding: 10px;
|
||||
border: solid 2px #d8d8d8;
|
||||
background-color: #fff;
|
||||
border-radius: 5px !important;
|
||||
color: #4a4a4a;
|
||||
}
|
||||
button {
|
||||
all: unset;
|
||||
height: 30px;
|
||||
font-size: 16px;
|
||||
padding: 0 10px;
|
||||
line-height: 30px;
|
||||
margin: 0 5px;
|
||||
color: #fff;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
#cancel {
|
||||
background-color: #999;
|
||||
}
|
||||
#confirm {
|
||||
background-color: #10a37f;
|
||||
}
|
||||
.item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
label {
|
||||
width: 120px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
</style>
|
||||
<h3>User Agent</h3>
|
||||
<div class="item">
|
||||
<label>Main Window (PC)</label>
|
||||
<textarea id="ua_pc" type="text" autocapitalize="off" autocomplete="off" spellcheck="false" autofocus placeholder="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ..."></textarea>
|
||||
</div>
|
||||
<div class="item">
|
||||
<label>Tray Window (Phone)</label>
|
||||
<textarea id="ua_phone" type="text" autocapitalize="off" autocomplete="off" spellcheck="false" autofocus placeholder="Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS ..."></textarea>
|
||||
</div>
|
||||
<div class="btns">
|
||||
<button id="cancel">Cancel</button>
|
||||
<button id="confirm">Confirm</button>
|
||||
</div>`;
|
||||
|
||||
const srcipt = document.createElement('script');
|
||||
srcipt.innerHTML = `const ua_pc = document.getElementById('ua_pc');
|
||||
const ua_phone = document.getElementById('ua_phone');
|
||||
const cancelBtn = document.getElementById('cancel');
|
||||
const confirmBtn = document.getElementById('confirm');
|
||||
cancelBtn.addEventListener('click', () => {
|
||||
window.invoke('form_cancel', { label: 'ua', title: 'User Agent', msg: 'Are you sure you want to cancel editing?' });
|
||||
})
|
||||
confirmBtn.addEventListener('click', () => {
|
||||
window.invoke('form_confirm', { data: { ua_pc: ua_pc.value, ua_phone: ua_phone.value } });
|
||||
})`;
|
||||
document.head.appendChild(srcipt);
|
||||
}
|
||||
|
||||
// run init
|
||||
if (
|
||||
document.readyState === "complete" ||
|
||||
document.readyState === "interactive"
|
||||
) {
|
||||
init();
|
||||
} else {
|
||||
document.addEventListener("DOMContentLoaded", init);
|
||||
}
|
||||
123
src-tauri/src/conf.rs
Normal file
@@ -0,0 +1,123 @@
|
||||
use crate::utils::{chat_root, create_file, exists};
|
||||
use anyhow::Result;
|
||||
use serde_json::Value;
|
||||
use std::{collections::BTreeMap, fs, path::PathBuf, sync::Mutex};
|
||||
use tauri::Theme;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
use tauri::TitleBarStyle;
|
||||
|
||||
pub const ISSUES_URL: &str = "https://github.com/lencx/ChatGPT/issues";
|
||||
pub const AWESOME_URL: &str = "https://github.com/lencx/ChatGPT/blob/main/AWESOME.md";
|
||||
pub const DEFAULT_CHAT_CONF: &str = r#"{
|
||||
"always_on_top": false,
|
||||
"theme": "Light",
|
||||
"titlebar": true,
|
||||
"default_origin": "https://chat.openai.com",
|
||||
"origin": "https://chat.openai.com",
|
||||
"ua_pc": "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1",
|
||||
"ua_phone": "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1"
|
||||
}"#;
|
||||
pub const DEFAULT_CHAT_CONF_MAC: &str = r#"{
|
||||
"always_on_top": false,
|
||||
"theme": "Light",
|
||||
"titlebar": false,
|
||||
"default_origin": "https://chat.openai.com",
|
||||
"origin": "https://chat.openai.com",
|
||||
"ua_pc": "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1",
|
||||
"ua_phone": "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1"
|
||||
}"#;
|
||||
|
||||
pub struct ChatState {
|
||||
pub always_on_top: Mutex<bool>,
|
||||
}
|
||||
|
||||
impl ChatState {
|
||||
pub fn default(chat_conf: ChatConfJson) -> Self {
|
||||
ChatState {
|
||||
always_on_top: Mutex::new(chat_conf.always_on_top),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
|
||||
pub struct ChatConfJson {
|
||||
pub titlebar: bool,
|
||||
pub always_on_top: bool,
|
||||
pub theme: String,
|
||||
pub default_origin: String,
|
||||
pub origin: String,
|
||||
pub ua_pc: String,
|
||||
pub ua_phone: String,
|
||||
}
|
||||
|
||||
impl ChatConfJson {
|
||||
/// init chat.conf.json
|
||||
/// path: ~/.chatgpt/chat.conf.json
|
||||
pub fn init() -> PathBuf {
|
||||
let conf_file = ChatConfJson::conf_path();
|
||||
if !exists(&conf_file) {
|
||||
create_file(&conf_file).unwrap();
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fs::write(&conf_file, DEFAULT_CHAT_CONF_MAC).unwrap();
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
fs::write(&conf_file, DEFAULT_CHAT_CONF).unwrap();
|
||||
}
|
||||
conf_file
|
||||
}
|
||||
|
||||
pub fn conf_path() -> PathBuf {
|
||||
chat_root().join("chat.conf.json")
|
||||
}
|
||||
|
||||
pub fn get_chat_conf() -> Self {
|
||||
let config_file = fs::read_to_string(ChatConfJson::conf_path())
|
||||
.unwrap_or_else(|_| DEFAULT_CHAT_CONF.to_string());
|
||||
let config: Value =
|
||||
serde_json::from_str(&config_file).expect("failed to parse chat.conf.json");
|
||||
serde_json::from_value(config).unwrap_or_else(|_| ChatConfJson::chat_conf_default())
|
||||
}
|
||||
|
||||
// https://users.rust-lang.org/t/updating-object-fields-given-dynamic-json/39049/3
|
||||
pub fn amend(new_rules: &Value) -> Result<()> {
|
||||
let config = ChatConfJson::get_chat_conf();
|
||||
let config: Value = serde_json::to_value(&config)?;
|
||||
let mut config: BTreeMap<String, Value> = serde_json::from_value(config)?;
|
||||
let new_rules: BTreeMap<String, Value> = serde_json::from_value(new_rules.clone())?;
|
||||
|
||||
for (k, v) in new_rules {
|
||||
config.insert(k, v);
|
||||
}
|
||||
|
||||
fs::write(
|
||||
ChatConfJson::conf_path(),
|
||||
serde_json::to_string_pretty(&config)?,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn theme() -> Option<Theme> {
|
||||
let conf = ChatConfJson::get_chat_conf();
|
||||
if conf.theme == "Dark" {
|
||||
Some(Theme::Dark)
|
||||
} else {
|
||||
Some(Theme::Light)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn titlebar() -> TitleBarStyle {
|
||||
let conf = ChatConfJson::get_chat_conf();
|
||||
if conf.titlebar {
|
||||
TitleBarStyle::Transparent
|
||||
} else {
|
||||
TitleBarStyle::Overlay
|
||||
}
|
||||
}
|
||||
|
||||
pub fn chat_conf_default() -> Self {
|
||||
serde_json::from_value(serde_json::json!(DEFAULT_CHAT_CONF)).unwrap()
|
||||
}
|
||||
}
|
||||
67
src-tauri/src/core.js
vendored
@@ -1,67 +0,0 @@
|
||||
// *** Core Script ***
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
const uid = () => window.crypto.getRandomValues(new Uint32Array(1))[0];
|
||||
function transformCallback(callback = () => {}, once = false) {
|
||||
const identifier = uid();
|
||||
const prop = `_${identifier}`;
|
||||
Object.defineProperty(window, prop, {
|
||||
value: (result) => {
|
||||
if (once) {
|
||||
Reflect.deleteProperty(window, prop);
|
||||
}
|
||||
return callback(result)
|
||||
},
|
||||
writable: false,
|
||||
configurable: true,
|
||||
})
|
||||
return identifier;
|
||||
}
|
||||
async function invoke(cmd, args) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!window.__TAURI_POST_MESSAGE__) reject('__TAURI_POST_MESSAGE__ does not exist!');
|
||||
const callback = transformCallback((e) => {
|
||||
resolve(e);
|
||||
Reflect.deleteProperty(window, `_${error}`);
|
||||
}, true)
|
||||
const error = transformCallback((e) => {
|
||||
reject(e);
|
||||
Reflect.deleteProperty(window, `_${callback}`);
|
||||
}, true)
|
||||
window.__TAURI_POST_MESSAGE__({
|
||||
cmd,
|
||||
callback,
|
||||
error,
|
||||
...args
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const topStyleDom = document.createElement("style");
|
||||
topStyleDom.innerHTML = `#chatgpt-app-window-top{position:fixed;top:0;z-index:999999999;width:100%;height:24px;background:transparent;cursor:grab;cursor:-webkit-grab;user-select:none;-webkit-user-select:none;}#chatgpt-app-window-top:active {cursor:grabbing;cursor:-webkit-grabbing;}`;
|
||||
document.head.appendChild(topStyleDom);
|
||||
const topDom = document.createElement("div");
|
||||
topDom.id = "chatgpt-app-window-top";
|
||||
document.body.appendChild(topDom);
|
||||
|
||||
topDom.addEventListener("mousedown", () => invoke("drag_window"));
|
||||
topDom.addEventListener("touchstart", () => invoke("drag_window"));
|
||||
topDom.addEventListener("dblclick", () => invoke("fullscreen"));
|
||||
|
||||
document.addEventListener("click", (e) => {
|
||||
const origin = e.target.closest("a");
|
||||
if (origin && origin.href && origin.target !== '_self') {
|
||||
origin.target = "_self";
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('wheel', function(event) {
|
||||
const deltaX = event.wheelDeltaX;
|
||||
if (Math.abs(deltaX) >= 50) {
|
||||
if (deltaX > 0) {
|
||||
window.history.go(-1);
|
||||
} else {
|
||||
window.history.go(1);
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
@@ -4,21 +4,45 @@
|
||||
)]
|
||||
|
||||
mod app;
|
||||
mod conf;
|
||||
mod utils;
|
||||
|
||||
use app::{cmd, menu, setup};
|
||||
use tauri::SystemTray;
|
||||
use conf::{ChatConfJson, ChatState};
|
||||
|
||||
fn main() {
|
||||
ChatConfJson::init();
|
||||
let chat_conf = ChatConfJson::get_chat_conf();
|
||||
let context = tauri::generate_context!();
|
||||
|
||||
tauri::Builder::default()
|
||||
.invoke_handler(tauri::generate_handler![cmd::drag_window, cmd::fullscreen])
|
||||
.manage(ChatState::default(chat_conf))
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
cmd::drag_window,
|
||||
cmd::fullscreen,
|
||||
cmd::download,
|
||||
cmd::open_link,
|
||||
cmd::get_chat_conf,
|
||||
cmd::form_cancel,
|
||||
cmd::form_confirm,
|
||||
cmd::form_msg,
|
||||
])
|
||||
.setup(setup::init)
|
||||
.plugin(tauri_plugin_positioner::init())
|
||||
.menu(menu::init(&context))
|
||||
.system_tray(SystemTray::new())
|
||||
.system_tray(menu::tray_menu())
|
||||
.on_menu_event(menu::menu_handler)
|
||||
.on_system_tray_event(menu::tray_handler)
|
||||
.on_window_event(|event| {
|
||||
// https://github.com/tauri-apps/tauri/discussions/2684
|
||||
if let tauri::WindowEvent::CloseRequested { api, .. } = event.event() {
|
||||
// TODO: https://github.com/tauri-apps/tauri/issues/3084
|
||||
// event.window().hide().unwrap();
|
||||
// https://github.com/tauri-apps/tao/pull/517
|
||||
event.window().minimize().unwrap();
|
||||
api.prevent_close();
|
||||
}
|
||||
})
|
||||
.run(context)
|
||||
.expect("error while running ChatGPT application");
|
||||
}
|
||||
|
||||
@@ -1,15 +1,23 @@
|
||||
use anyhow::Result;
|
||||
use std::fs::{self, File};
|
||||
use std::path::{Path, PathBuf};
|
||||
use tauri::utils::config::Config;
|
||||
use std::{
|
||||
fs::{self, File},
|
||||
path::{Path, PathBuf},
|
||||
process::Command,
|
||||
};
|
||||
use tauri::Manager;
|
||||
// use tauri::utils::config::Config;
|
||||
|
||||
pub fn get_tauri_conf() -> Option<Config> {
|
||||
let config_file = include_str!("../tauri.conf.json");
|
||||
let config: Config =
|
||||
serde_json::from_str(config_file).expect("failed to parse tauri.conf.json");
|
||||
Some(config)
|
||||
pub fn chat_root() -> PathBuf {
|
||||
tauri::api::path::home_dir().unwrap().join(".chatgpt")
|
||||
}
|
||||
|
||||
// pub fn get_tauri_conf() -> Option<Config> {
|
||||
// let config_file = include_str!("../tauri.conf.json");
|
||||
// let config: Config =
|
||||
// serde_json::from_str(config_file).expect("failed to parse tauri.conf.json");
|
||||
// Some(config)
|
||||
// }
|
||||
|
||||
pub fn exists(path: &Path) -> bool {
|
||||
Path::new(path).exists()
|
||||
}
|
||||
@@ -22,8 +30,7 @@ pub fn create_file(path: &Path) -> Result<File> {
|
||||
}
|
||||
|
||||
pub fn script_path() -> PathBuf {
|
||||
let root = tauri::api::path::home_dir().unwrap().join(".chatgpt");
|
||||
let script_file = root.join("main.js");
|
||||
let script_file = chat_root().join("main.js");
|
||||
if !exists(&script_file) {
|
||||
create_file(&script_file).unwrap();
|
||||
fs::write(&script_file, format!("// *** ChatGPT User Script ***\n// @github: https://github.com/lencx/ChatGPT \n// @path: {}\n\nconsole.log('🤩 Hello ChatGPT!!!');", &script_file.to_string_lossy())).unwrap();
|
||||
@@ -39,3 +46,36 @@ pub fn user_script() -> String {
|
||||
user_script_content
|
||||
)
|
||||
}
|
||||
|
||||
pub fn open_file(path: PathBuf) {
|
||||
#[cfg(target_os = "macos")]
|
||||
Command::new("open").arg("-R").arg(path).spawn().unwrap();
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
Command::new("explorer")
|
||||
.arg("/select,")
|
||||
.arg(path)
|
||||
.spawn()
|
||||
.unwrap();
|
||||
|
||||
// https://askubuntu.com/a/31071
|
||||
#[cfg(target_os = "linux")]
|
||||
Command::new("xdg-open").arg(path).spawn().unwrap();
|
||||
}
|
||||
|
||||
pub fn clear_conf(app: &tauri::AppHandle) {
|
||||
let root = chat_root();
|
||||
let app2 = app.clone();
|
||||
let msg = format!("Path: {}\nAre you sure to clear all ChatGPT configurations? Please backup in advance if necessary!", root.to_string_lossy());
|
||||
tauri::api::dialog::ask(
|
||||
app.get_window("core").as_ref(),
|
||||
"Clear Config",
|
||||
msg,
|
||||
move |is_ok| {
|
||||
if is_ok {
|
||||
fs::remove_dir_all(root).unwrap();
|
||||
tauri::api::process::restart(&app2.env());
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,20 +2,20 @@
|
||||
"build": {
|
||||
"beforeDevCommand": "",
|
||||
"beforeBuildCommand": "",
|
||||
"devPath": "https://chat.openai.com",
|
||||
"devPath": "../dist",
|
||||
"distDir": "../dist"
|
||||
},
|
||||
"package": {
|
||||
"productName": "ChatGPT",
|
||||
"version": "0.1.1"
|
||||
"version": "0.2.0"
|
||||
},
|
||||
"tauri": {
|
||||
"allowlist": {
|
||||
"all": true
|
||||
},
|
||||
"systemTray": {
|
||||
"iconPath": "icons/icon.png",
|
||||
"iconAsTemplate": false
|
||||
"iconPath": "icons/tray-icon.png",
|
||||
"iconAsTemplate": true
|
||||
},
|
||||
"bundle": {
|
||||
"active": true,
|
||||
@@ -44,6 +44,10 @@
|
||||
"shortDescription": "ChatGPT",
|
||||
"targets": "all",
|
||||
"windows": {
|
||||
"webviewInstallMode": {
|
||||
"silent": true,
|
||||
"type": "downloadBootstrapper"
|
||||
},
|
||||
"certificateThumbprint": null,
|
||||
"digestAlgorithm": "sha256",
|
||||
"timestampUrl": ""
|
||||
|
||||