Compare commits

...

20 Commits

Author SHA1 Message Date
lencx
c6efaec586 v0.10.3 2023-02-03 13:34:42 +08:00
lencx
01c6e11e8c release 2023-02-03 13:33:26 +08:00
lencx
70aac07f8e feat: markdown export (#233) 2023-02-03 12:22:15 +08:00
lencx
4847bb6fac chore: copy 2023-02-03 01:30:03 +08:00
lencx
041ffb7462 fix: conf error (#295) 2023-02-03 01:11:56 +08:00
lencx
93bb98e9c0 Merge pull request #292 from lencx/dev 2023-02-02 01:12:06 +08:00
lencx
11877a960d v0.10.2 2023-02-02 00:46:33 +08:00
lencx
a107e2b21e release 2023-02-02 00:46:23 +08:00
lencx
62924975a6 fix: export buttons (#286) 2023-02-02 00:39:30 +08:00
lencx
8aeca251e6 fix: shortcuts (#254) 2023-02-02 00:04:38 +08:00
lencx
272ef1cd37 feat: copy to clipboard (#191) 2023-02-01 23:48:35 +08:00
lencx
8eade8e9b9 chore: optim 2023-02-01 00:26:41 +08:00
lencx
171ac94f77 chore: optim 2023-01-30 23:55:43 +08:00
lencx
ffef57e934 fix: export buttons do not work (#274) 2023-01-29 23:25:23 +08:00
lencx
7e499b6b1e Merge pull request #266 from lencx/dev 2023-01-27 23:10:46 +08:00
lencx
a38804139a chore: log 2023-01-27 17:44:01 +08:00
lencx
a939b3442e add visitor 2023-01-27 11:52:10 +08:00
lencx
074afb58c8 Merge pull request #256 from lencx/dev 2023-01-27 00:26:38 +08:00
lencx
f9f173407e refactor: app conf 2023-01-27 00:23:05 +08:00
lencx
99877209a1 Merge pull request #250 from lencx/dev 2023-01-25 20:30:06 +08:00
23 changed files with 635 additions and 544 deletions

View File

@@ -1,26 +0,0 @@
# Awesome ChatGPT
- [Awesome ChatGPT Prompts](https://github.com/f/awesome-chatgpt-prompts) - This repo includes ChatGPT prompt curation to use ChatGPT better.
- [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.
- [ChatGPT-Google](https://github.com/ZohaibAhmed/ChatGPT-Google) - Chrome Extension that Integrates ChatGPT (Unofficial) into Google Search
`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
## Tools
- [commitgpt](https://github.com/RomanHotsiy/commitgpt) - Automatically generate commit messages using ChatGPT
- [ShareGPT](https://sharegpt.com/) - ShareGPT: Share your wildest ChatGPT conversations with one click.

View File

@@ -24,7 +24,7 @@
### Windows
- [ChatGPT_0.10.1_x64_en-US.msi](https://github.com/lencx/ChatGPT/releases/download/v0.10.1/ChatGPT_0.10.1_x64_en-US.msi):
- [ChatGPT_0.10.3_x64_en-US.msi](https://github.com/lencx/ChatGPT/releases/download/v0.10.3/ChatGPT_0.10.3_x64_en-US.msi):
- 使用 [winget](https://winstall.app/apps/lencx.ChatGPT):
```bash
@@ -35,12 +35,12 @@
winget install --id=lencx.ChatGPT -e --version 0.10.0
```
**注意:如果安装路径和应用名称相同,会导致冲突 ([#142](https://github.com/lencx/ChatGPT/issues/142#issuecomment-0.10.1))**
**注意:如果安装路径和应用名称相同,会导致冲突 ([#142](https://github.com/lencx/ChatGPT/issues/142#issuecomment-0.10.3))**
### Mac
- [ChatGPT_0.10.1_x64.dmg](https://github.com/lencx/ChatGPT/releases/download/v0.10.1/ChatGPT_0.10.1_x64.dmg)
- [ChatGPT.app.tar.gz](https://github.com/lencx/ChatGPT/releases/download/v0.10.1/ChatGPT.app.tar.gz)
- [ChatGPT_0.10.3_x64.dmg](https://github.com/lencx/ChatGPT/releases/download/v0.10.3/ChatGPT_0.10.3_x64.dmg)
- [ChatGPT.app.tar.gz](https://github.com/lencx/ChatGPT/releases/download/v0.10.3/ChatGPT.app.tar.gz)
- Homebrew \
_[Homebrew 快捷安装](https://brew.sh) ([Cask](https://docs.brew.sh/Cask-Cookbook)):_
```sh
@@ -56,8 +56,8 @@
### Linux
- [chat-gpt_0.10.1_amd64.deb](https://github.com/lencx/ChatGPT/releases/download/v0.10.1/chat-gpt_0.10.1_amd64.deb)
- [chat-gpt_0.10.1_amd64.AppImage](https://github.com/lencx/ChatGPT/releases/download/v0.10.1/chat-gpt_0.10.1_amd64.AppImage): **工作可靠,`.deb` 运行失败时可以尝试它**
- [chat-gpt_0.10.3_amd64.deb](https://github.com/lencx/ChatGPT/releases/download/v0.10.3/chat-gpt_0.10.3_amd64.deb)
- [chat-gpt_0.10.3_amd64.AppImage](https://github.com/lencx/ChatGPT/releases/download/v0.10.3/chat-gpt_0.10.3_amd64.AppImage): **工作可靠,`.deb` 运行失败时可以尝试它**
- 使用 [AUR](https://aur.archlinux.org/packages/chatgpt-desktop-bin):
```bash
yay -S chatgpt-desktop-bin

View File

@@ -7,6 +7,7 @@
[![English badge](https://img.shields.io/badge/%E8%8B%B1%E6%96%87-English-blue)](./README.md)
[![简体中文 badge](https://img.shields.io/badge/%E7%AE%80%E4%BD%93%E4%B8%AD%E6%96%87-Simplified%20Chinese-blue)](./README-ZH_CN.md)\
![License](https://img.shields.io/badge/License-Apache%202-green.svg)
![visitor](https://visitor-badge.glitch.me/badge?page_id=lencx.chatgpt)
[![ChatGPT downloads](https://img.shields.io/github/downloads/lencx/ChatGPT/total.svg?style=flat-square)](https://github.com/lencx/ChatGPT/releases)
[![chat](https://img.shields.io/badge/chat-discord-blue?style=flat&logo=discord)](https://discord.gg/aPhCRf4zZr)
[![lencx](https://img.shields.io/badge/follow-lencx__-blue?style=flat&logo=Twitter)](https://twitter.com/lencx_)
@@ -26,7 +27,7 @@
### Windows
- [ChatGPT_0.10.1_x64_en-US.msi](https://github.com/lencx/ChatGPT/releases/download/v0.10.1/ChatGPT_0.10.1_x64_en-US.msi): Direct download installer
- [ChatGPT_0.10.3_x64_en-US.msi](https://github.com/lencx/ChatGPT/releases/download/v0.10.3/ChatGPT_0.10.3_x64_en-US.msi): Direct download installer
- Use [winget](https://winstall.app/apps/lencx.ChatGPT):
```bash
@@ -37,12 +38,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.10.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.10.3))**
### Mac
- [ChatGPT_0.10.1_x64.dmg](https://github.com/lencx/ChatGPT/releases/download/v0.10.1/ChatGPT_0.10.1_x64.dmg): Direct download installer
- [ChatGPT.app.tar.gz](https://github.com/lencx/ChatGPT/releases/download/v0.10.1/ChatGPT.app.tar.gz): Download the `.app` installer
- [ChatGPT_0.10.3_x64.dmg](https://github.com/lencx/ChatGPT/releases/download/v0.10.3/ChatGPT_0.10.3_x64.dmg): Direct download installer
- [ChatGPT.app.tar.gz](https://github.com/lencx/ChatGPT/releases/download/v0.10.3/ChatGPT.app.tar.gz): Download the `.app` installer
- Homebrew \
Or you can install with _[Homebrew](https://brew.sh) ([Cask](https://docs.brew.sh/Cask-Cookbook)):_
```sh
@@ -58,8 +59,8 @@
### Linux
- [chat-gpt_0.10.1_amd64.deb](https://github.com/lencx/ChatGPT/releases/download/v0.10.1/chat-gpt_0.10.1_amd64.deb): Download `.deb` installer, advantage small size, disadvantage poor compatibility
- [chat-gpt_0.10.1_amd64.AppImage](https://github.com/lencx/ChatGPT/releases/download/v0.10.1/chat-gpt_0.10.1_amd64.AppImage): Works reliably, you can try it if `.deb` fails to run
- [chat-gpt_0.10.3_amd64.deb](https://github.com/lencx/ChatGPT/releases/download/v0.10.3/chat-gpt_0.10.3_amd64.deb): Download `.deb` installer, advantage small size, disadvantage poor compatibility
- [chat-gpt_0.10.3_amd64.AppImage](https://github.com/lencx/ChatGPT/releases/download/v0.10.3/chat-gpt_0.10.3_amd64.AppImage): Works reliably, you can try it if `.deb` fails to run
- Available on [AUR](https://aur.archlinux.org/packages/chatgpt-desktop-bin) with the package name `chatgpt-desktop-bin`, and you can use your favourite AUR package manager to install it.
<!-- download end -->

View File

@@ -1,5 +1,32 @@
# UPDATE LOG
## v0.10.3
> Note: As of now the ChatGPT desktop app has added a lot of exciting features and it continues to improve, as the app grows it has gone far beyond what ChatGPT was intended for. I want to make it the ultimate goal that any website can be easily wrapped to the desktop through user customization. So it needed an international user guide to guide users to use it more professionally. And https://app.nofwl.com is the manual for the app, which will be built into the app (`Menu -> Window -> ChatGPT User's Guide`) so you can access it anytime. It's just starting at the moment, so stay tuned.
Fix:
- Incompatible configuration data causes program crashes (https://github.com/lencx/ChatGPT/issues/295)
Feat:
- Silent copy text
- Markdown export support distinguishes between users and bots (https://github.com/lencx/ChatGPT/issues/233)
## v0.10.2
Fix:
- PNG and PDF buttons do not work (https://github.com/lencx/ChatGPT/issues/274)
- Change the window size and the Send button is obscured by the Export button (https://github.com/lencx/ChatGPT/issues/286)
- Change forward and backward shortcuts (https://github.com/lencx/ChatGPT/issues/254)
- MacOS: `Cmd [`, `Cmd ]`
- Windows and Linux: `Ctrl [`, `Ctrl ]`
Feat:
- Copy a single record to the clipboard (https://github.com/lencx/ChatGPT/issues/191)
## v0.10.1
Fix:

View File

@@ -1,3 +0,0 @@
# ChatGPT Model
- [Awesome ChatGPT Prompts](https://github.com/f/awesome-chatgpt-prompts)

View File

@@ -1,10 +1,7 @@
use crate::{
conf::ChatConfJson,
utils::{self, chat_root, create_file},
};
use log::info;
use crate::utils;
use log::error;
use std::{fs, path::PathBuf};
use tauri::{api, command, AppHandle, Manager, Theme};
use tauri::{api, command, AppHandle, Manager};
#[command]
pub fn drag_window(app: AppHandle) {
@@ -22,19 +19,29 @@ pub fn fullscreen(app: AppHandle) {
}
#[command]
pub fn download(_app: AppHandle, name: String, blob: Vec<u8>) {
let path = chat_root().join(PathBuf::from(name));
create_file(&path).unwrap();
pub fn download(app: AppHandle, name: String, blob: Vec<u8>) {
let win = app.app_handle().get_window("core");
let path = utils::app_root().join(PathBuf::from(name));
utils::create_file(&path).unwrap();
fs::write(&path, blob).unwrap();
utils::open_file(path);
tauri::api::dialog::message(
win.as_ref(),
"Save File",
format!("PATH: {}", path.display()),
);
}
#[command]
pub fn save_file(_app: AppHandle, name: String, content: String) {
let path = chat_root().join(PathBuf::from(name));
create_file(&path).unwrap();
pub fn save_file(app: AppHandle, name: String, content: String) {
let win = app.app_handle().get_window("core");
let path = utils::app_root().join(PathBuf::from(name));
utils::create_file(&path).unwrap();
fs::write(&path, content).unwrap();
utils::open_file(path);
tauri::api::dialog::message(
win.as_ref(),
"Save File",
format!("PATH: {}", path.display()),
);
}
#[command]
@@ -42,52 +49,11 @@ 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 reset_chat_conf() -> ChatConfJson {
ChatConfJson::reset_chat_conf()
}
#[command]
pub fn get_theme() -> String {
ChatConfJson::theme().unwrap_or(Theme::Light).to_string()
}
#[command]
pub fn run_check_update(app: AppHandle, silent: bool, has_msg: Option<bool>) {
utils::run_check_update(app, silent, has_msg);
}
#[command]
pub fn form_confirm(_app: AppHandle, data: serde_json::Value) {
ChatConfJson::amend(&serde_json::json!(data), None).unwrap();
}
#[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);
}
#[command]
pub fn open_file(path: PathBuf) {
utils::open_file(path);
@@ -102,7 +68,7 @@ pub async fn get_data(app: AppHandle, url: String, is_msg: Option<bool>) -> Opti
utils::get_data(&url, None).await
};
res.unwrap_or_else(|err| {
info!("chatgpt_client_http_error: {}", err);
error!("chatgpt_client_http: {}", err);
None
})
}

View File

@@ -1,9 +1,9 @@
use crate::{
app::{fs_extra, window},
conf::GITHUB_PROMPTS_CSV_URL,
utils::{self, chat_root},
utils,
};
use log::info;
use log::{error, info};
use regex::Regex;
use std::{collections::HashMap, fs, path::PathBuf, vec};
use tauri::{api, command, AppHandle, Manager};
@@ -11,7 +11,7 @@ use walkdir::WalkDir;
#[command]
pub fn get_chat_model_cmd() -> serde_json::Value {
let path = utils::chat_root().join("chat.model.cmd.json");
let path = utils::app_root().join("chat.model.cmd.json");
let content = fs::read_to_string(path).unwrap_or_else(|_| r#"{"data":[]}"#.to_string());
serde_json::from_str(&content).unwrap()
}
@@ -29,7 +29,7 @@ pub fn parse_prompt(data: String) -> Vec<PromptRecord> {
let mut list = vec![];
for result in rdr.deserialize() {
let record: PromptRecord = result.unwrap_or_else(|err| {
info!("parse_prompt_error: {}", err);
error!("parse_prompt: {}", err);
PromptRecord {
cmd: None,
act: "".to_string(),
@@ -55,7 +55,7 @@ pub struct ModelRecord {
#[command]
pub fn cmd_list() -> Vec<ModelRecord> {
let mut list = vec![];
for entry in WalkDir::new(utils::chat_root().join("cache_model"))
for entry in WalkDir::new(utils::app_root().join("cache_model"))
.into_iter()
.filter_map(|e| e.ok())
{
@@ -82,14 +82,14 @@ pub struct FileMetadata {
#[tauri::command]
pub fn get_download_list(pathname: &str) -> (Vec<serde_json::Value>, PathBuf) {
info!("get_download_list: {}", pathname);
let download_path = chat_root().join(PathBuf::from(pathname));
let download_path = utils::app_root().join(PathBuf::from(pathname));
let content = fs::read_to_string(&download_path).unwrap_or_else(|err| {
info!("download_list_error: {}", err);
error!("download_list: {}", err);
fs::write(&download_path, "[]").unwrap();
"[]".to_string()
});
let list = serde_json::from_str::<Vec<serde_json::Value>>(&content).unwrap_or_else(|err| {
info!("download_list_parse_error: {}", err);
error!("download_list_parse: {}", err);
vec![]
});
@@ -104,7 +104,7 @@ pub fn download_list(pathname: &str, dir: &str, filename: Option<String>, id: Op
let mut idmap = HashMap::new();
utils::vec_to_hashmap(data.0.into_iter(), "id", &mut idmap);
for entry in WalkDir::new(utils::chat_root().join(dir))
for entry in WalkDir::new(utils::app_root().join(dir))
.into_iter()
.filter_entry(|e| !utils::is_hidden(e))
.filter_map(|e| e.ok())
@@ -182,9 +182,9 @@ pub async fn sync_prompts(app: AppHandle, time: u64) -> Option<Vec<ModelRecord>>
let data2 = data.clone();
let model = utils::chat_root().join("chat.model.json");
let model_cmd = utils::chat_root().join("chat.model.cmd.json");
let chatgpt_prompts = utils::chat_root()
let model = utils::app_root().join("chat.model.json");
let model_cmd = utils::app_root().join("chat.model.cmd.json");
let chatgpt_prompts = utils::app_root()
.join("cache_model")
.join("chatgpt_prompts.json");
@@ -238,8 +238,8 @@ pub async fn sync_prompts(app: AppHandle, time: u64) -> Option<Vec<ModelRecord>>
"Sync Prompts",
"ChatGPT Prompts data has been synchronized!",
);
window::window_reload(app.clone(), "core");
window::window_reload(app, "tray");
window::cmd::window_reload(app.clone(), "core");
window::cmd::window_reload(app, "tray");
return Some(data2);
}
@@ -249,13 +249,12 @@ pub async fn sync_prompts(app: AppHandle, time: u64) -> Option<Vec<ModelRecord>>
#[command]
pub async fn sync_user_prompts(url: String, data_type: String) -> Option<Vec<ModelRecord>> {
info!("sync_user_prompts: url => {}", url);
let res = utils::get_data(&url, None).await.unwrap_or_else(|err| {
info!("chatgpt_http_error: {}", err);
error!("chatgpt_http: {}", err);
None
});
info!("chatgpt_http_url: {}", url);
if let Some(v) = res {
let data;
if data_type == "csv" {
@@ -264,11 +263,11 @@ pub async fn sync_user_prompts(url: String, data_type: String) -> Option<Vec<Mod
} else if data_type == "json" {
info!("chatgpt_http_json_parse");
data = serde_json::from_str(&v).unwrap_or_else(|err| {
info!("chatgpt_http_json_parse_error: {}", err);
error!("chatgpt_http_json_parse: {}", err);
vec![]
});
} else {
info!("chatgpt_http_unknown_type");
error!("chatgpt_http_unknown_type");
data = vec![];
}

View File

@@ -1,6 +1,6 @@
use crate::{
app::window,
conf::{self, ChatConfJson},
conf::{self, AppConf},
utils,
};
use tauri::{
@@ -14,7 +14,7 @@ use tauri::AboutMetadata;
// --- Menu
pub fn init() -> Menu {
let chat_conf = ChatConfJson::get_chat_conf();
let app_conf = AppConf::read();
let name = "ChatGPT";
let app_menu = Submenu::new(
name,
@@ -35,7 +35,7 @@ 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_menu = if chat_conf.stay_on_top {
let stay_on_top_menu = if app_conf.stay_on_top {
stay_on_top.selected()
} else {
stay_on_top
@@ -44,15 +44,15 @@ pub fn init() -> Menu {
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 is_dark = chat_conf.theme == "Dark";
let is_system = chat_conf.theme == "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_disable = CustomMenuItem::new("update_disable".to_string(), "Disable");
let popup_search = CustomMenuItem::new("popup_search".to_string(), "Pop-up Search");
let popup_search_menu = if chat_conf.popup_search {
let popup_search_menu = if app_conf.popup_search {
popup_search.selected()
} else {
popup_search
@@ -61,19 +61,20 @@ pub fn init() -> Menu {
#[cfg(target_os = "macos")]
let titlebar = CustomMenuItem::new("titlebar".to_string(), "Titlebar").accelerator("CmdOrCtrl+B");
#[cfg(target_os = "macos")]
let titlebar_menu = if chat_conf.titlebar {
let titlebar_menu = if app_conf.titlebar {
titlebar.selected()
} else {
titlebar
};
let system_tray = CustomMenuItem::new("system_tray".to_string(), "System Tray");
let system_tray_menu = if chat_conf.tray {
let system_tray_menu = if app_conf.tray {
system_tray.selected()
} else {
system_tray
};
let auto_update = app_conf.get_auto_update();
let preferences_menu = Submenu::new(
"Preferences",
Menu::with_items([
@@ -114,16 +115,16 @@ pub fn init() -> Menu {
Submenu::new(
"Auto Update",
Menu::new()
.add_item(if chat_conf.auto_update == "Prompt" {
.add_item(if auto_update == "prompt" {
update_prompt.selected()
} else {
update_prompt
})
.add_item(if chat_conf.auto_update == "Silent" {
.add_item(if auto_update == "silent" {
update_silent.selected()
} else {
update_silent
}), // .add_item(if chat_conf.auto_update == "Disable" {
}), // .add_item(if auto_update == "disable" {
// update_disable.selected()
// } else {
// update_disable
@@ -142,7 +143,6 @@ pub fn init() -> Menu {
.into(),
CustomMenuItem::new("clear_conf".to_string(), "Clear Config").into(),
MenuItem::Separator.into(),
CustomMenuItem::new("awesome".to_string(), "Awesome ChatGPT").into(),
CustomMenuItem::new("buy_coffee".to_string(), "Buy lencx a coffee").into(),
]),
);
@@ -162,9 +162,9 @@ pub fn init() -> Menu {
let view_menu = Submenu::new(
"View",
Menu::new()
.add_item(CustomMenuItem::new("go_back".to_string(), "Go Back").accelerator("CmdOrCtrl+Left"))
.add_item(CustomMenuItem::new("go_back".to_string(), "Go Back").accelerator("CmdOrCtrl+["))
.add_item(
CustomMenuItem::new("go_forward".to_string(), "Go Forward").accelerator("CmdOrCtrl+Right"),
CustomMenuItem::new("go_forward".to_string(), "Go Forward").accelerator("CmdOrCtrl+]"),
)
.add_item(
CustomMenuItem::new("scroll_top".to_string(), "Scroll to Top of Screen")
@@ -189,6 +189,10 @@ pub fn init() -> Menu {
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_native_item(MenuItem::Separator)
.add_native_item(MenuItem::Minimize)
@@ -241,23 +245,31 @@ pub fn menu_handler(event: WindowMenuEvent<tauri::Wry>) {
utils::run_check_update(app, false, None);
}
// Preferences
"control_center" => window::control_window(app),
"control_center" => window::cmd::control_window(app),
"restart" => tauri::api::process::restart(&app.env()),
"inject_script" => open(&app, script_path),
"go_conf" => utils::open_file(utils::chat_root()),
"go_conf" => utils::open_file(utils::app_root()),
"clear_conf" => utils::clear_conf(&app),
"awesome" => open(&app, conf::AWESOME_URL.to_string()),
"app_website" => window::cmd::wa_window(
app,
"app_website".into(),
"ChatGPT User's Guide".into(),
conf::APP_WEBSITE.into(),
None,
),
"buy_coffee" => open(&app, conf::BUY_COFFEE.to_string()),
"popup_search" => {
let chat_conf = conf::ChatConfJson::get_chat_conf();
let popup_search = !chat_conf.popup_search;
let app_conf = AppConf::read();
let popup_search = !app_conf.popup_search;
menu_handle
.get_item(menu_id)
.set_selected(popup_search)
.unwrap();
ChatConfJson::amend(&serde_json::json!({ "popup_search": popup_search }), None).unwrap();
window::window_reload(app.clone(), "core");
window::window_reload(app, "tray");
app_conf
.amend(serde_json::json!({ "popup_search": popup_search }))
.write();
window::cmd::window_reload(app.clone(), "core");
window::cmd::window_reload(app, "tray");
}
"sync_prompts" => {
tauri::api::dialog::ask(
@@ -276,29 +288,37 @@ pub fn menu_handler(event: WindowMenuEvent<tauri::Wry>) {
);
}
"hide_dock_icon" => {
ChatConfJson::amend(&serde_json::json!({ "hide_dock_icon": true }), Some(app)).unwrap()
AppConf::read()
.amend(serde_json::json!({ "hide_dock_icon": true }))
.write()
.restart(app);
}
"titlebar" => {
let chat_conf = conf::ChatConfJson::get_chat_conf();
ChatConfJson::amend(
&serde_json::json!({ "titlebar": !chat_conf.titlebar }),
None,
)
.unwrap();
tauri::api::process::restart(&app.env());
let app_conf = AppConf::read();
app_conf
.clone()
.amend(serde_json::json!({ "titlebar": !app_conf.titlebar }))
.write()
.restart(app);
}
"system_tray" => {
let chat_conf = conf::ChatConfJson::get_chat_conf();
ChatConfJson::amend(&serde_json::json!({ "tray": !chat_conf.tray }), None).unwrap();
tauri::api::process::restart(&app.env());
let app_conf = AppConf::read();
app_conf
.clone()
.amend(serde_json::json!({ "tray": !app_conf.tray }))
.write()
.restart(app);
}
"theme_light" | "theme_dark" | "theme_system" => {
let theme = match menu_id {
"theme_dark" => "Dark",
"theme_system" => "System",
_ => "Light",
"theme_dark" => "dark",
"theme_system" => "system",
_ => "light",
};
ChatConfJson::amend(&serde_json::json!({ "theme": theme }), Some(app)).unwrap();
AppConf::read()
.amend(serde_json::json!({ "theme": theme }))
.write()
.restart(app);
}
"update_prompt" | "update_silent" | "update_disable" => {
// for id in ["update_prompt", "update_silent", "update_disable"] {
@@ -311,34 +331,38 @@ pub fn menu_handler(event: WindowMenuEvent<tauri::Wry>) {
.get_item("update_silent")
.set_selected(true)
.unwrap();
"Silent"
"silent"
}
"update_disable" => {
menu_handle
.get_item("update_disable")
.set_selected(true)
.unwrap();
"Disable"
"disable"
}
_ => {
menu_handle
.get_item("update_prompt")
.set_selected(true)
.unwrap();
"Prompt"
"prompt"
}
};
ChatConfJson::amend(&serde_json::json!({ "auto_update": auto_update }), None).unwrap();
AppConf::read()
.amend(serde_json::json!({ "auto_update": auto_update }))
.write();
}
"stay_on_top" => {
let chat_conf = conf::ChatConfJson::get_chat_conf();
let stay_on_top = !chat_conf.stay_on_top;
let app_conf = AppConf::read();
let stay_on_top = !app_conf.stay_on_top;
menu_handle
.get_item(menu_id)
.set_selected(stay_on_top)
.unwrap();
win.set_always_on_top(stay_on_top).unwrap();
ChatConfJson::amend(&serde_json::json!({ "stay_on_top": stay_on_top }), None).unwrap();
app_conf
.amend(serde_json::json!({ "stay_on_top": stay_on_top }))
.write();
}
// Window
"dalle2" => window::dalle2_window(&app, None, None, Some(false)),
@@ -367,7 +391,7 @@ pub fn menu_handler(event: WindowMenuEvent<tauri::Wry>) {
)
.unwrap(),
// Help
"chatgpt_log" => utils::open_file(utils::chat_root().join("chatgpt.log")),
"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()),
"dev_tools" => {
@@ -381,22 +405,29 @@ pub fn menu_handler(event: WindowMenuEvent<tauri::Wry>) {
// --- SystemTray Menu
pub fn tray_menu() -> SystemTray {
if cfg!(target_os = "macos") {
SystemTray::new().with_menu(
SystemTrayMenu::new()
.add_item(CustomMenuItem::new(
"control_center".to_string(),
"Control Center",
))
.add_native_item(SystemTrayMenuItem::Separator)
.add_item(CustomMenuItem::new(
"show_dock_icon".to_string(),
"Show Dock Icon",
))
let mut tray_menu = SystemTrayMenu::new()
.add_item(CustomMenuItem::new(
"control_center".to_string(),
"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",
));
} 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 ChatGPT"))
.add_item(CustomMenuItem::new("show_core".to_string(), "Show ChatGPT"));
}
SystemTray::new().with_menu(
tray_menu
.add_native_item(SystemTrayMenuItem::Separator)
.add_item(CustomMenuItem::new("quit".to_string(), "Quit ChatGPT")),
)
@@ -422,42 +453,51 @@ pub fn tray_handler(handle: &AppHandle, event: SystemTrayEvent) {
match event {
SystemTrayEvent::LeftClick { .. } => {
let chat_conf = conf::ChatConfJson::get_chat_conf();
let app_conf = AppConf::read();
if !chat_conf.hide_dock_icon {
let core_win = handle.get_window("core").unwrap();
core_win.minimize().unwrap();
if !app_conf.hide_dock_icon {
if let Some(core_win) = handle.get_window("core") {
core_win.minimize().unwrap();
}
}
let tray_win = handle.get_window("tray").unwrap();
tray_win.move_window(Position::TrayCenter).unwrap();
if let Some(tray_win) = handle.get_window("tray") {
tray_win.move_window(Position::TrayCenter).unwrap();
if tray_win.is_visible().unwrap() {
tray_win.hide().unwrap();
} else {
tray_win.show().unwrap();
if tray_win.is_visible().unwrap() {
tray_win.hide().unwrap();
} else {
tray_win.show().unwrap();
}
}
}
SystemTrayEvent::MenuItemClick { id, .. } => match id.as_str() {
"control_center" => window::control_window(app),
"control_center" => window::cmd::control_window(app),
"restart" => tauri::api::process::restart(&handle.env()),
"show_dock_icon" => {
ChatConfJson::amend(&serde_json::json!({ "hide_dock_icon": false }), Some(app)).unwrap();
AppConf::read()
.amend(serde_json::json!({ "hide_dock_icon": false }))
.write()
.restart(app);
}
"hide_dock_icon" => {
let chat_conf = conf::ChatConfJson::get_chat_conf();
if !chat_conf.hide_dock_icon {
ChatConfJson::amend(&serde_json::json!({ "hide_dock_icon": true }), Some(app)).unwrap();
let app_conf = AppConf::read();
if !app_conf.hide_dock_icon {
app_conf
.amend(serde_json::json!({ "hide_dock_icon": true }))
.write()
.restart(app);
}
}
"show_core" => {
let core_win = app.get_window("core").unwrap();
let tray_win = app.get_window("tray").unwrap();
if !core_win.is_visible().unwrap() {
core_win.show().unwrap();
core_win.set_focus().unwrap();
tray_win.hide().unwrap();
}
if let Some(core_win) = app.get_window("core") {
let tray_win = app.get_window("tray").unwrap();
if !core_win.is_visible().unwrap() {
core_win.show().unwrap();
core_win.set_focus().unwrap();
tray_win.hide().unwrap();
}
};
}
"quit" => std::process::exit(0),
_ => (),

View File

@@ -1,20 +1,21 @@
use crate::{app::window, conf::ChatConfJson, utils};
use log::info;
use crate::{app::window, conf::AppConf, utils};
use log::{error, info};
use tauri::{utils::config::WindowUrl, window::WindowBuilder, App, GlobalShortcutManager, Manager};
use wry::application::accelerator::Accelerator;
pub fn init(app: &mut App) -> std::result::Result<(), Box<dyn std::error::Error>> {
info!("stepup");
let chat_conf = ChatConfJson::get_chat_conf();
let url = chat_conf.main_origin.to_string();
let theme = ChatConfJson::theme();
let app_conf = AppConf::read();
let url = app_conf.main_origin.to_string();
let theme = AppConf::theme_mode();
let handle = app.app_handle();
tauri::async_runtime::spawn(async move {
info!("stepup_tray");
window::tray_window(&handle);
});
if let Some(v) = chat_conf.global_shortcut {
if let Some(v) = app_conf.clone().global_shortcut {
info!("global_shortcut: `{}`", v);
match v.parse::<Accelerator>() {
Ok(_) => {
@@ -33,47 +34,49 @@ pub fn init(app: &mut App) -> std::result::Result<(), Box<dyn std::error::Error>
}
})
.unwrap_or_else(|err| {
info!("global_shortcut_register_error: {}", err);
error!("global_shortcut_register_error: {}", err);
});
}
Err(err) => {
info!("global_shortcut_parse_error: {}", err);
error!("global_shortcut_parse_error: {}", err);
}
}
} else {
info!("global_shortcut_unregister");
};
if chat_conf.hide_dock_icon {
let app_conf2 = app_conf.clone();
if app_conf.hide_dock_icon {
#[cfg(target_os = "macos")]
app.set_activation_policy(tauri::ActivationPolicy::Accessory);
} else {
let app = app.handle();
tauri::async_runtime::spawn(async move {
let link = if chat_conf.main_dashboard {
let link = if app_conf2.main_dashboard {
"index.html"
} else {
&url
};
info!("main_window: {}", link);
let mut main_win = WindowBuilder::new(&app, "core", WindowUrl::App(link.into()))
.title("ChatGPT")
.resizable(true)
.fullscreen(false)
.inner_size(800.0, 600.0)
.theme(theme)
.always_on_top(chat_conf.stay_on_top)
.theme(Some(theme))
.always_on_top(app_conf2.stay_on_top)
.initialization_script(&utils::user_script())
.initialization_script(include_str!("../scripts/core.js"))
.user_agent(&chat_conf.ua_window);
.user_agent(&app_conf2.ua_window);
#[cfg(target_os = "macos")]
{
main_win = main_win
.title_bar_style(ChatConfJson::titlebar())
.title_bar_style(app_conf2.clone().titlebar())
.hidden_title(true);
}
if url == "https://chat.openai.com" && !chat_conf.main_dashboard {
if url == "https://chat.openai.com" && !app_conf2.main_dashboard {
main_win = main_win
.initialization_script(include_str!("../vendors/floating-ui-core.js"))
.initialization_script(include_str!("../vendors/floating-ui-dom.js"))
@@ -92,10 +95,11 @@ pub fn init(app: &mut App) -> std::result::Result<(), Box<dyn std::error::Error>
}
// auto_update
if chat_conf.auto_update != "Disable" {
info!("stepup::run_check_update");
let auto_update = app_conf.get_auto_update();
if auto_update != "disable" {
info!("run_check_update");
let app = app.handle();
utils::run_check_update(app, chat_conf.auto_update == "Silent", None);
utils::run_check_update(app, auto_update == "silent", None);
}
Ok(())

View File

@@ -1,18 +1,18 @@
use crate::{conf, utils};
use crate::{conf::AppConf, utils};
use log::info;
use std::time::SystemTime;
use tauri::{utils::config::WindowUrl, window::WindowBuilder, Manager};
pub fn tray_window(handle: &tauri::AppHandle) {
let chat_conf = conf::ChatConfJson::get_chat_conf();
let theme = conf::ChatConfJson::theme();
let app_conf = AppConf::read();
let theme = AppConf::theme_mode();
let app = handle.clone();
tauri::async_runtime::spawn(async move {
let link = if chat_conf.tray_dashboard {
let link = if app_conf.tray_dashboard {
"index.html"
} else {
&chat_conf.tray_origin
&app_conf.tray_origin
};
let mut tray_win = WindowBuilder::new(&app, "tray", WindowUrl::App(link.into()))
.title("ChatGPT")
@@ -21,12 +21,12 @@ pub fn tray_window(handle: &tauri::AppHandle) {
.inner_size(360.0, 540.0)
.decorations(false)
.always_on_top(true)
.theme(theme)
.theme(Some(theme))
.initialization_script(&utils::user_script())
.initialization_script(include_str!("../scripts/core.js"))
.user_agent(&chat_conf.ua_tray);
.user_agent(&app_conf.ua_tray);
if chat_conf.tray_origin == "https://chat.openai.com" && !chat_conf.tray_dashboard {
if app_conf.tray_origin == "https://chat.openai.com" && !app_conf.tray_dashboard {
tray_win = tray_win
.initialization_script(include_str!("../vendors/floating-ui-core.js"))
.initialization_script(include_str!("../vendors/floating-ui-dom.js"))
@@ -45,7 +45,7 @@ pub fn dalle2_window(
is_new: Option<bool>,
) {
info!("dalle2_query: {:?}", query);
let theme = conf::ChatConfJson::theme();
let theme = AppConf::theme_mode();
let app = handle.clone();
let query = if query.is_some() {
@@ -76,7 +76,7 @@ pub fn dalle2_window(
.fullscreen(false)
.inner_size(800.0, 600.0)
.always_on_top(false)
.theme(theme)
.theme(Some(theme))
.initialization_script(include_str!("../scripts/core.js"))
.initialization_script(&query)
.initialization_script(include_str!("../scripts/dalle2.js"))
@@ -90,78 +90,83 @@ pub fn dalle2_window(
}
}
#[tauri::command]
pub fn dalle2_search_window(app: tauri::AppHandle, query: String) {
dalle2_window(
&app.app_handle(),
Some(query),
Some("ChatGPT & DALL·E 2".to_string()),
None,
);
}
pub mod cmd {
use super::*;
use log::info;
use tauri::{command, utils::config::WindowUrl, window::WindowBuilder, Manager};
#[tauri::command]
pub fn control_window(handle: tauri::AppHandle) {
tauri::async_runtime::spawn(async move {
if handle.get_window("main").is_none() {
WindowBuilder::new(
&handle,
"main",
WindowUrl::App("index.html?type=control".into()),
)
.title("Control Center")
.resizable(true)
.fullscreen(false)
.inner_size(1200.0, 700.0)
.min_inner_size(1000.0, 600.0)
.build()
.unwrap();
} else {
let main_win = handle.get_window("main").unwrap();
main_win.show().unwrap();
main_win.set_focus().unwrap();
}
});
}
#[tauri::command]
pub fn dalle2_search_window(app: tauri::AppHandle, query: String) {
dalle2_window(
&app.app_handle(),
Some(query),
Some("ChatGPT & DALL·E 2".to_string()),
None,
);
}
#[tauri::command]
pub async fn wa_window(
app: tauri::AppHandle,
label: String,
title: String,
url: String,
script: Option<String>,
) {
info!("wa_window: {} :=> {}", title, url);
let win = app.get_window(&label);
if win.is_none() {
#[tauri::command]
pub fn control_window(handle: tauri::AppHandle) {
tauri::async_runtime::spawn(async move {
tauri::WindowBuilder::new(&app, label, tauri::WindowUrl::App(url.parse().unwrap()))
.initialization_script(&script.unwrap_or_default())
.initialization_script(include_str!("../scripts/core.js"))
.title(title)
if handle.get_window("main").is_none() {
WindowBuilder::new(
&handle,
"main",
WindowUrl::App("index.html?type=control".into()),
)
.title("Control Center")
.resizable(true)
.fullscreen(false)
.inner_size(1200.0, 700.0)
.min_inner_size(1000.0, 600.0)
.build()
.unwrap();
} else {
let main_win = handle.get_window("main").unwrap();
main_win.show().unwrap();
main_win.set_focus().unwrap();
}
});
} else {
if !win.clone().unwrap().is_visible().unwrap() {
win.clone().unwrap().show().unwrap();
}
#[command]
pub fn wa_window(
app: tauri::AppHandle,
label: String,
title: String,
url: String,
script: Option<String>,
) {
info!("wa_window: {} :=> {}", title, url);
let win = app.get_window(&label);
if win.is_none() {
tauri::async_runtime::spawn(async move {
tauri::WindowBuilder::new(&app, label, tauri::WindowUrl::App(url.parse().unwrap()))
.initialization_script(&script.unwrap_or_default())
.initialization_script(include_str!("../scripts/core.js"))
.title(title)
.inner_size(960.0, 700.0)
.resizable(true)
.build()
.unwrap();
});
}
win
.clone()
if let Some(v) = win {
if !v.is_visible().unwrap() {
v.show().unwrap();
}
v.eval("window.location.reload()").unwrap();
v.set_focus().unwrap();
}
}
#[command]
pub fn window_reload(app: tauri::AppHandle, label: &str) {
app
.app_handle()
.get_window(label)
.unwrap()
.eval("window.location.reload()")
.unwrap();
win.unwrap().set_focus().unwrap();
}
}
#[tauri::command]
pub fn window_reload(app: tauri::AppHandle, label: &str) {
app
.app_handle()
.get_window(label)
.unwrap()
.eval("window.location.reload()")
.unwrap();
}

View File

@@ -1,179 +1,149 @@
use crate::utils::{chat_root, create_file, exists};
use anyhow::Result;
use log::info;
use log::{error, info};
use serde_json::Value;
use std::{collections::BTreeMap, fs, path::PathBuf};
use std::{collections::BTreeMap, path::PathBuf};
use tauri::{Manager, Theme};
#[cfg(target_os = "macos")]
use tauri::TitleBarStyle;
// pub const USER_AGENT: &str = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.1 Safari/605.1.15";
// pub const PHONE_USER_AGENT: &str = "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1";
use crate::utils::{app_root, create_file, exists};
pub const APP_WEBSITE: &str = "https://lencx.github.io/app/";
pub const ISSUES_URL: &str = "https://github.com/lencx/ChatGPT/issues";
pub const UPDATE_LOG_URL: &str = "https://github.com/lencx/ChatGPT/blob/main/UPDATE_LOG.md";
pub const AWESOME_URL: &str = "https://github.com/lencx/ChatGPT/blob/main/AWESOME.md";
pub const BUY_COFFEE: &str = "https://www.buymeacoffee.com/lencx";
pub const GITHUB_PROMPTS_CSV_URL: &str =
"https://raw.githubusercontent.com/f/awesome-chatgpt-prompts/main/prompts.csv";
pub const DEFAULT_CHAT_CONF: &str = r#"{
"stay_on_top": false,
"auto_update": "Prompt",
"theme": "Light",
"tray": true,
"titlebar": true,
"popup_search": false,
"global_shortcut": "",
"hide_dock_icon": false,
"main_dashboard": false,
"tray_dashboard": false,
"main_origin": "https://chat.openai.com",
"tray_origin": "https://chat.openai.com",
"default_origin": "https://chat.openai.com",
"ua_window": "",
"ua_tray": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1"
}"#;
pub const DEFAULT_CHAT_CONF_MAC: &str = r#"{
"stay_on_top": false,
"auto_update": "Prompt",
"theme": "Light",
"tray": true,
"titlebar": false,
"popup_search": false,
"global_shortcut": "",
"hide_dock_icon": false,
"main_dashboard": false,
"tray_dashboard": false,
"main_origin": "https://chat.openai.com",
"tray_origin": "https://chat.openai.com",
"default_origin": "https://chat.openai.com",
"ua_window": "",
"ua_tray": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1"
}"#;
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
pub struct ChatConfJson {
// support macOS only
pub titlebar: bool,
pub hide_dock_icon: bool,
pub const APP_CONF_PATH: &str = "chat.conf.json";
pub const CHATGPT_URL: &str = "https://chat.openai.com";
pub const UA_MOBILE: &str = "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1";
// macOS and Windows, Light/Dark/System
pub theme: String,
// auto update policy, Prompt/Silent/Disable
pub auto_update: String,
pub tray: bool,
pub popup_search: bool,
pub stay_on_top: bool,
pub main_dashboard: bool,
pub tray_dashboard: bool,
pub main_origin: String,
pub tray_origin: String,
pub default_origin: String,
pub ua_window: String,
pub ua_tray: String,
pub global_shortcut: Option<String>,
macro_rules! pub_struct {
($name:ident {$($field:ident: $t:ty,)*}) => {
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
pub struct $name {
$(pub $field: $t),*
}
}
}
impl ChatConfJson {
/// init chat.conf.json
/// path: ~/.chatgpt/chat.conf.json
pub fn init() -> PathBuf {
info!("chat_conf_init");
let conf_file = ChatConfJson::conf_path();
let content = if cfg!(target_os = "macos") {
DEFAULT_CHAT_CONF_MAC
} else {
DEFAULT_CHAT_CONF
};
pub_struct!(AppConf {
titlebar: bool,
hide_dock_icon: bool,
// macOS and Windows: light / dark / system
theme: String,
// auto update policy: prompt / silent / disable
auto_update: String,
tray: bool,
popup_search: bool,
stay_on_top: bool,
main_dashboard: bool,
tray_dashboard: bool,
main_origin: String,
tray_origin: String,
default_origin: String,
ua_window: String,
ua_tray: String,
global_shortcut: Option<String>,
});
if !exists(&conf_file) {
create_file(&conf_file).unwrap();
fs::write(&conf_file, content).unwrap();
return conf_file;
impl AppConf {
pub fn new() -> Self {
info!("conf_init");
Self {
titlebar: !cfg!(target_os = "macos"),
hide_dock_icon: false,
theme: "light".into(),
auto_update: "prompt".into(),
tray: true,
popup_search: false,
stay_on_top: false,
main_dashboard: false,
tray_dashboard: false,
main_origin: CHATGPT_URL.into(),
tray_origin: CHATGPT_URL.into(),
default_origin: CHATGPT_URL.into(),
ua_tray: UA_MOBILE.into(),
ua_window: "".into(),
global_shortcut: None,
}
}
let conf_file = ChatConfJson::conf_path();
let file_content = fs::read_to_string(&conf_file).unwrap();
match serde_json::from_str(&file_content) {
Ok(v) => v,
Err(err) => {
if err.to_string() == "invalid type: map, expected unit at line 1 column 0" {
return conf_file;
pub fn file_path() -> PathBuf {
app_root().join(APP_CONF_PATH)
}
pub fn read() -> Self {
match std::fs::read_to_string(Self::file_path()) {
Ok(v) => {
if let Ok(v2) = serde_json::from_str::<AppConf>(&v) {
v2
} else {
error!("conf_read_parse_error");
Self::default()
}
fs::write(&conf_file, content).unwrap();
}
};
conf_file
}
pub fn conf_path() -> PathBuf {
chat_root().join("chat.conf.json")
}
pub fn get_chat_conf() -> Self {
let conf_file = ChatConfJson::conf_path();
let file_content = fs::read_to_string(&conf_file).unwrap();
let content = if cfg!(target_os = "macos") {
DEFAULT_CHAT_CONF_MAC
} else {
DEFAULT_CHAT_CONF
};
match serde_json::from_value(match serde_json::from_str(&file_content) {
Ok(v) => v,
Err(_) => {
fs::write(&conf_file, content).unwrap();
serde_json::from_str(content).unwrap()
}
}) {
Ok(v) => v,
Err(_) => {
fs::write(&conf_file, content).unwrap();
serde_json::from_value(serde_json::from_str(content).unwrap()).unwrap()
Err(err) => {
error!("conf_read_error: {}", err);
Self::default()
}
}
}
pub fn reset_chat_conf() -> Self {
let conf_file = ChatConfJson::conf_path();
let content = if cfg!(target_os = "macos") {
DEFAULT_CHAT_CONF_MAC
pub fn write(self) -> Self {
let path = &Self::file_path();
if !exists(path) {
create_file(path).unwrap();
info!("conf_create");
}
if let Ok(v) = serde_json::to_string_pretty(&self) {
std::fs::write(path, v).unwrap_or_else(|err| {
error!("conf_write: {}", err);
Self::default().write();
});
} else {
DEFAULT_CHAT_CONF
};
fs::write(&conf_file, content).unwrap();
serde_json::from_str(content).unwrap()
error!("conf_ser");
}
self
}
// https://users.rust-lang.org/t/updating-object-fields-given-dynamic-json/39049/3
pub fn amend(new_rules: &Value, app: Option<tauri::AppHandle>) -> 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())?;
pub fn amend(self, json: Value) -> Self {
let val = serde_json::to_value(&self).unwrap();
let mut config: BTreeMap<String, Value> = serde_json::from_value(val).unwrap();
let new_json: BTreeMap<String, Value> = serde_json::from_value(json).unwrap();
for (k, v) in new_rules {
for (k, v) in new_json {
config.insert(k, v);
}
fs::write(
ChatConfJson::conf_path(),
serde_json::to_string_pretty(&config)?,
)?;
if let Some(handle) = app {
tauri::api::process::restart(&handle.env());
match serde_json::to_string_pretty(&config) {
Ok(v) => match serde_json::from_str::<AppConf>(&v) {
Ok(v) => v,
Err(err) => {
error!("conf_amend_parse: {}", err);
self
}
},
Err(err) => {
error!("conf_amend_str: {}", err);
self
}
}
Ok(())
}
pub fn theme() -> Option<Theme> {
let conf = ChatConfJson::get_chat_conf();
let theme = match conf.theme.as_str() {
"System" => match dark_light::detect() {
#[cfg(target_os = "macos")]
pub fn titlebar(self) -> TitleBarStyle {
if self.titlebar {
TitleBarStyle::Transparent
} else {
TitleBarStyle::Overlay
}
}
pub fn theme_mode() -> Theme {
match Self::get_theme().as_str() {
"system" => match dark_light::detect() {
// Dark mode
dark_light::Mode::Dark => Theme::Dark,
// Light mode
@@ -181,20 +151,76 @@ impl ChatConfJson {
// Unspecified
dark_light::Mode::Default => Theme::Light,
},
"Dark" => Theme::Dark,
"dark" => Theme::Dark,
_ => Theme::Light,
};
Some(theme)
}
#[cfg(target_os = "macos")]
pub fn titlebar() -> TitleBarStyle {
let conf = ChatConfJson::get_chat_conf();
if conf.titlebar {
TitleBarStyle::Transparent
} else {
TitleBarStyle::Overlay
}
}
pub fn get_theme() -> String {
Self::read().theme.to_lowercase()
}
pub fn get_auto_update(self) -> String {
self.auto_update.to_lowercase()
}
pub fn theme_check(self, mode: &str) -> bool {
self.theme.to_lowercase() == mode
}
pub fn restart(self, app: tauri::AppHandle) {
tauri::api::process::restart(&app.env());
}
}
impl Default for AppConf {
fn default() -> Self {
Self::new()
}
}
pub mod cmd {
use super::AppConf;
use tauri::{command, AppHandle, Manager};
#[command]
pub fn get_app_conf() -> AppConf {
AppConf::read()
}
#[command]
pub fn reset_app_conf() -> AppConf {
AppConf::default().write()
}
#[command]
pub fn get_theme() -> String {
AppConf::get_theme()
}
#[command]
pub fn form_confirm(_app: AppHandle, data: serde_json::Value) {
AppConf::read().amend(serde_json::json!(data)).write();
}
#[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);
}
}

View File

@@ -8,8 +8,7 @@ mod conf;
mod utils;
use app::{cmd, fs_extra, gpt, menu, setup, window};
use conf::ChatConfJson;
use tauri::api::path;
use conf::AppConf;
use tauri_plugin_autostart::MacosLauncher;
use tauri_plugin_log::{
fern::colors::{Color, ColoredLevelConfig},
@@ -18,38 +17,36 @@ use tauri_plugin_log::{
#[tokio::main]
async fn main() {
ChatConfJson::init();
let app_conf = AppConf::read().write();
// If the file does not exist, creating the file will block menu synchronization
utils::create_chatgpt_prompts();
let context = tauri::generate_context!();
let colors = ColoredLevelConfig {
error: Color::Red,
warn: Color::Yellow,
debug: Color::Blue,
info: Color::BrightGreen,
trace: Color::Cyan,
};
gpt::download_list("chat.download.json", "download", None, None);
gpt::download_list("chat.notes.json", "notes", None, None);
let chat_conf = ChatConfJson::get_chat_conf();
let mut log = tauri_plugin_log::Builder::default()
.targets([
// LogTarget::LogDir,
// LOG PATH: ~/.chatgpt/ChatGPT.log
LogTarget::Folder(utils::app_root()),
LogTarget::Stdout,
LogTarget::Webview,
])
.level(log::LevelFilter::Debug);
if cfg!(debug_assertions) {
log = log.with_colors(ColoredLevelConfig {
error: Color::Red,
warn: Color::Yellow,
debug: Color::Blue,
info: Color::BrightGreen,
trace: Color::Cyan,
});
}
let mut builder = tauri::Builder::default()
// https://github.com/tauri-apps/tauri/pull/2736
.plugin(
tauri_plugin_log::Builder::default()
.targets([
// LogTarget::LogDir,
// LOG PATH: ~/.chatgpt/ChatGPT.log
LogTarget::Folder(path::home_dir().unwrap().join(".chatgpt")),
LogTarget::Stdout,
LogTarget::Webview,
])
.level(log::LevelFilter::Debug)
.with_colors(colors)
.build(),
)
.plugin(log.build())
.plugin(tauri_plugin_positioner::init())
.plugin(tauri_plugin_autostart::init(
MacosLauncher::LaunchAgent,
@@ -61,13 +58,7 @@ async fn main() {
cmd::download,
cmd::save_file,
cmd::open_link,
cmd::get_chat_conf,
cmd::get_theme,
cmd::reset_chat_conf,
cmd::run_check_update,
cmd::form_cancel,
cmd::form_confirm,
cmd::form_msg,
cmd::open_file,
cmd::get_data,
gpt::get_chat_model_cmd,
@@ -77,16 +68,22 @@ async fn main() {
gpt::cmd_list,
gpt::download_list,
gpt::get_download_list,
window::wa_window,
window::control_window,
window::window_reload,
window::dalle2_search_window,
fs_extra::metadata,
conf::cmd::get_app_conf,
conf::cmd::reset_app_conf,
conf::cmd::get_theme,
conf::cmd::form_confirm,
conf::cmd::form_cancel,
conf::cmd::form_msg,
window::cmd::wa_window,
window::cmd::control_window,
window::cmd::window_reload,
window::cmd::dalle2_search_window,
])
.setup(setup::init)
.menu(menu::init());
if chat_conf.tray {
if app_conf.tray {
builder = builder.system_tray(menu::tray_menu());
}
@@ -94,19 +91,10 @@ async fn main() {
.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() {
let win = event.window();
if win.label() == "core" {
// TODO: https://github.com/tauri-apps/tauri/issues/3084
// event.window().hide().unwrap();
// https://github.com/tauri-apps/tao/pull/517
#[cfg(target_os = "macos")]
event.window().minimize().unwrap();
// fix: https://github.com/lencx/ChatGPT/issues/93
#[cfg(not(target_os = "macos"))]
event.window().hide().unwrap();
} else {
win.close().unwrap();
}

View File

@@ -75,6 +75,14 @@ function init() {
width: 22px;
height: 22px;
}
.chatappico.copy {
width: 16px;
height: 16px;
}
.chatappico.cpok {
width: 16px;
height: 16px;
}
@media screen and (max-width: 767px) {
#download-png-button, #download-pdf-button, #download-html-button {
display: none;

View File

@@ -36,8 +36,22 @@ async function invoke(cmd, args) {
});
}
async function message(message) {
invoke('messageDialog', {
__tauriModule: 'Dialog',
message: {
cmd: 'messageDialog',
message: message.toString(),
title: null,
type: null,
buttonLabel: null
}
});
}
window.uid = uid;
window.invoke = invoke;
window.message = message;
window.transformCallback = transformCallback;
async function init() {
@@ -54,7 +68,7 @@ async function init() {
if (__TAURI_METADATA__.__currentWindow.label !== 'tray') {
const _platform = await platform();
const chatConf = await invoke('get_chat_conf') || {};
const chatConf = await invoke('get_app_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;}`;
@@ -87,17 +101,6 @@ async function init() {
}
});
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 (window.location.host === 'chat.openai.com') {
window.__sync_prompts = async function() {
await invoke('sync_prompts', { time: Date.now() });

View File

@@ -2,11 +2,13 @@
async function init() {
const buttonOuterHTMLFallback = `<button class="btn flex justify-center gap-2 btn-neutral" id="download-png-button">Try Again</button>`;
if (window.innerWidth < 767) return;
const chatConf = await invoke('get_chat_conf') || {};
removeButtons();
if (window.buttonsInterval) {
clearInterval(window.buttonsInterval);
}
if (window.innerWidth < 767) return;
const chatConf = await invoke('get_app_conf') || {};
window.buttonsInterval = setInterval(() => {
const actionsArea = document.querySelector("form>div>div");
if (!actionsArea) {
@@ -20,12 +22,15 @@ async function init() {
TryAgainButton = parentNode.querySelector("button");
}
addActionsButtons(actionsArea, TryAgainButton, chatConf);
copyBtns();
} else if (shouldRemoveButtons()) {
removeButtons();
}
}, 1000);
}
window.addEventListener('resize', init);
const Format = {
PNG: "png",
PDF: "pdf",
@@ -132,7 +137,14 @@ function addActionsButtons(actionsArea, TryAgainButton) {
}
async function exportMarkdown() {
const data = ExportMD.turndown(document.querySelector("main div>div>div").innerHTML);
const content = Array.from(document.querySelectorAll("main >div>div>div>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('<hr />');
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' });
@@ -200,7 +212,7 @@ class Elements {
}
init() {
// this.threadWrapper = document.querySelector(".cdfdFe");
this.spacer = document.querySelector(".w-full.h-48.flex-shrink-0");
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"
);
@@ -268,10 +280,49 @@ function setIcon(type) {
// 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" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1380" 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>`
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

@@ -2,7 +2,9 @@ var ExportMD = (function () {
if (!TurndownService || !turndownPluginGfm) return;
const hljsREG = /^.*(hljs).*(language-[a-z0-9]+).*$/i;
const gfm = turndownPluginGfm.gfm
const turndownService = new TurndownService()
const turndownService = new TurndownService({
hr: '---'
})
.use(gfm)
.addRule('code', {
filter: (node) => {

View File

@@ -1,7 +1,7 @@
// *** Core Script - DALL·E 2 Core ***
async function init() {
const chatConf = await invoke('get_chat_conf') || {};
const chatConf = await invoke('get_app_conf') || {};
if (!chatConf.popup_search) return;
if (!window.FloatingUIDOM) return;

View File

@@ -1,5 +1,5 @@
use anyhow::Result;
use log::info;
use log::{error, info};
use regex::Regex;
use serde_json::Value;
use std::{
@@ -11,7 +11,7 @@ use std::{
use tauri::updater::UpdateResponse;
use tauri::{utils::config::Config, AppHandle, Manager, Wry};
pub fn chat_root() -> PathBuf {
pub fn app_root() -> PathBuf {
tauri::api::path::home_dir().unwrap().join(".chatgpt")
}
@@ -33,7 +33,7 @@ pub fn create_file(path: &Path) -> Result<File> {
}
pub fn create_chatgpt_prompts() {
let sync_file = chat_root().join("cache_model").join("chatgpt_prompts.json");
let sync_file = app_root().join("cache_model").join("chatgpt_prompts.json");
if !exists(&sync_file) {
create_file(&sync_file).unwrap();
fs::write(&sync_file, "[]").unwrap();
@@ -41,7 +41,7 @@ pub fn create_chatgpt_prompts() {
}
pub fn script_path() -> PathBuf {
let script_file = chat_root().join("main.js");
let script_file = app_root().join("main.js");
if !exists(&script_file) {
create_file(&script_file).unwrap();
fs::write(
@@ -96,7 +96,7 @@ pub fn convert_path(path_str: &str) -> String {
}
pub fn clear_conf(app: &tauri::AppHandle) {
let root = chat_root();
let root = app_root();
let msg = format!(
"Path: {}\n
Are you sure you want to clear all ChatGPT configurations? Performing this operation data can not be restored, please back up in advance.\n
@@ -145,7 +145,7 @@ pub async fn get_data(
if is_ok {
Ok(Some(body))
} else {
info!("chatgpt_http_error: {}", body);
error!("chatgpt_http: {}", body);
if let Some(v) = app {
tauri::api::dialog::message(v.get_window("core").as_ref(), "ChatGPT HTTP", body);
}
@@ -156,25 +156,25 @@ pub async fn get_data(
pub fn run_check_update(app: AppHandle<Wry>, silent: bool, has_msg: Option<bool>) {
info!("run_check_update: silent={} has_msg={:?}", silent, has_msg);
tauri::async_runtime::spawn(async move {
let result = app.updater().check().await;
let update_resp = result.unwrap();
if update_resp.is_update_available() {
if silent {
tauri::async_runtime::spawn(async move {
silent_install(app, update_resp).await.unwrap();
});
} else {
tauri::async_runtime::spawn(async move {
prompt_for_install(app, update_resp).await.unwrap();
});
}
} else if let Some(v) = has_msg {
if v {
tauri::api::dialog::message(
app.app_handle().get_window("core").as_ref(),
"ChatGPT",
"Your ChatGPT is up to date",
);
if let Ok(update_resp) = app.updater().check().await {
if update_resp.is_update_available() {
if silent {
tauri::async_runtime::spawn(async move {
silent_install(app, update_resp).await.unwrap();
});
} else {
tauri::async_runtime::spawn(async move {
prompt_for_install(app, update_resp).await.unwrap();
});
}
} else if let Some(v) = has_msg {
if v {
tauri::api::dialog::message(
app.app_handle().get_window("core").as_ref(),
"ChatGPT",
"Your ChatGPT is up to date",
);
}
}
}
});

View File

@@ -7,7 +7,7 @@
},
"package": {
"productName": "ChatGPT",
"version": "0.10.1"
"version": "0.10.3"
},
"tauri": {
"allowlist": {

2
src/utils.ts vendored
View File

@@ -2,7 +2,7 @@ import { readTextFile, writeTextFile, exists, createDir } from '@tauri-apps/api/
import { homeDir, join, dirname } from '@tauri-apps/api/path';
import dayjs from 'dayjs';
export const CHAT_CONF_JSON = 'chat.conf.json';
export const APP_CONF_JSON = 'chat.conf.json';
export const CHAT_MODEL_JSON = 'chat.model.json';
export const CHAT_MODEL_CMD_JSON = 'chat.model.cmd.json';
export const CHAT_DOWNLOAD_JSON = 'chat.download.json';

View File

@@ -7,7 +7,7 @@ import { os, invoke } from '@tauri-apps/api';
import useInit from '@/hooks/useInit';
import useJson from '@/hooks/useJson';
import { CHAT_AWESOME_JSON, CHAT_CONF_JSON, readJSON } from '@/utils';
import { CHAT_AWESOME_JSON, APP_CONF_JSON, readJSON } from '@/utils';
import './index.scss';
export default function Dashboard() {
@@ -19,7 +19,7 @@ export default function Dashboard() {
useInit(async () => {
const getOS = await os.platform();
const conf = await readJSON(CHAT_CONF_JSON);
const conf = await readJSON(APP_CONF_JSON);
const appTheme = await invoke('get_theme');
setTheme(appTheme as string);
setClass(!conf?.titlebar && getOS === 'darwin');

View File

@@ -30,16 +30,16 @@ export default function General() {
)}
<Form.Item label="Theme" name="theme">
<Radio.Group>
<Radio value="Light">Light</Radio>
<Radio value="Dark">Dark</Radio>
<Radio value="light">Light</Radio>
<Radio value="dark">Dark</Radio>
{['darwin', 'windows'].includes(platformInfo) && <Radio value="System">System</Radio>}
</Radio.Group>
</Form.Item>
<Form.Item label={<AutoUpdateLabel />} name="auto_update">
<Radio.Group>
<Radio value="Prompt">Prompt</Radio>
<Radio value="Silent">Silent</Radio>
{/*<Radio value="Disable">Disable</Radio>*/}
<Radio value="prompt">Prompt</Radio>
<Radio value="silent">Silent</Radio>
{/*<Radio value="disable">Disable</Radio>*/}
</Radio.Group>
</Form.Item>
<Form.Item label={<GlobalShortcutLabel />} name="global_shortcut">

View File

@@ -6,7 +6,7 @@ import { clone, omit, isEqual } from 'lodash';
import useInit from '@/hooks/useInit';
import FilePath from '@/components/FilePath';
import { chatRoot, CHAT_CONF_JSON } from '@/utils';
import { chatRoot, APP_CONF_JSON } from '@/utils';
import General from './General';
import MainWindow from './MainWindow';
import TrayWindow from './TrayWindow';
@@ -24,8 +24,8 @@ export default function Settings() {
}, [key]);
useInit(async () => {
setChatConf(await invoke('get_chat_conf'));
setPath(await path.join(await chatRoot(), CHAT_CONF_JSON));
setChatConf(await invoke('get_app_conf'));
setPath(await path.join(await chatRoot(), APP_CONF_JSON));
});
useEffect(() => {
@@ -37,7 +37,7 @@ export default function Settings() {
};
const onReset = async () => {
const chatData = await invoke('reset_chat_conf');
const chatData = await invoke('reset_app_conf');
setChatConf(chatData);
const isOk = await dialog.ask(`Configuration reset successfully, whether to restart?`, {
title: 'ChatGPT Preferences',
@@ -69,7 +69,7 @@ export default function Settings() {
return (
<div>
<FilePath paths={CHAT_CONF_JSON} />
<FilePath paths={APP_CONF_JSON} />
<Form
form={form}
style={{ maxWidth: 500 }}