diff --git a/src-tauri/build.rs b/src-tauri/build.rs index d860e1e..795b9b7 100644 --- a/src-tauri/build.rs +++ b/src-tauri/build.rs @@ -1,3 +1,3 @@ fn main() { - tauri_build::build() + tauri_build::build() } diff --git a/src-tauri/rustfmt.toml b/src-tauri/rustfmt.toml new file mode 100644 index 0000000..7c27671 --- /dev/null +++ b/src-tauri/rustfmt.toml @@ -0,0 +1,4 @@ +edition = "2021" +max_width = 120 +tab_spaces = 2 +newline_style = "Auto" \ No newline at end of file diff --git a/src-tauri/src/app/cmd.rs b/src-tauri/src/app/cmd.rs index b14aebd..80b0d04 100644 --- a/src-tauri/src/app/cmd.rs +++ b/src-tauri/src/app/cmd.rs @@ -1,7 +1,7 @@ use crate::{ - app::{fs_extra, window}, - conf::{ChatConfJson, GITHUB_PROMPTS_CSV_URL}, - utils::{self, chat_root, create_file}, + app::{fs_extra, window}, + conf::{ChatConfJson, GITHUB_PROMPTS_CSV_URL}, + utils::{self, chat_root, create_file}, }; use log::info; use regex::Regex; @@ -11,390 +11,383 @@ use walkdir::WalkDir; #[command] pub fn drag_window(app: AppHandle) { - app.get_window("core").unwrap().start_dragging().unwrap(); + app.get_window("core").unwrap().start_dragging().unwrap(); } #[command] pub fn dalle2_window(app: AppHandle, query: String) { - window::dalle2_window( - &app.app_handle(), - Some(query), - Some("ChatGPT & DALL·E 2".to_string()), - None, - ); + window::dalle2_window( + &app.app_handle(), + Some(query), + Some("ChatGPT & DALL·E 2".to_string()), + None, + ); } #[command] pub fn fullscreen(app: AppHandle) { - let win = app.get_window("core").unwrap(); - if win.is_fullscreen().unwrap() { - win.set_fullscreen(false).unwrap(); - } else { - win.set_fullscreen(true).unwrap(); - } + let win = app.get_window("core").unwrap(); + if win.is_fullscreen().unwrap() { + win.set_fullscreen(false).unwrap(); + } else { + win.set_fullscreen(true).unwrap(); + } } #[command] pub fn download(_app: AppHandle, name: String, blob: Vec) { - let path = chat_root().join(PathBuf::from(name)); - create_file(&path).unwrap(); - fs::write(&path, blob).unwrap(); - utils::open_file(path); + let path = chat_root().join(PathBuf::from(name)); + create_file(&path).unwrap(); + fs::write(&path, blob).unwrap(); + utils::open_file(path); } #[command] pub fn save_file(_app: AppHandle, name: String, content: String) { - let path = chat_root().join(PathBuf::from(name)); - create_file(&path).unwrap(); - fs::write(&path, content).unwrap(); - utils::open_file(path); + let path = chat_root().join(PathBuf::from(name)); + create_file(&path).unwrap(); + fs::write(&path, content).unwrap(); + utils::open_file(path); } #[command] pub fn open_link(app: AppHandle, url: String) { - api::shell::open(&app.shell_scope(), url, None).unwrap(); + api::shell::open(&app.shell_scope(), url, None).unwrap(); } #[command] pub fn get_chat_conf() -> ChatConfJson { - ChatConfJson::get_chat_conf() + ChatConfJson::get_chat_conf() } #[command] pub fn get_theme() -> String { - ChatConfJson::theme().unwrap_or(Theme::Light).to_string() + ChatConfJson::theme().unwrap_or(Theme::Light).to_string() } #[command] pub fn reset_chat_conf() -> ChatConfJson { - ChatConfJson::reset_chat_conf() + ChatConfJson::reset_chat_conf() } #[command] pub fn run_check_update(app: AppHandle, silent: bool, has_msg: Option) { - utils::run_check_update(app, silent, has_msg); + 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(); + 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(); - } - }, - ); + 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); + 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); + utils::open_file(path); } #[command] pub fn get_chat_model_cmd() -> serde_json::Value { - let path = utils::chat_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() + let path = utils::chat_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() } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct PromptRecord { - pub cmd: Option, - pub act: String, - pub prompt: String, + pub cmd: Option, + pub act: String, + pub prompt: String, } #[command] pub fn parse_prompt(data: String) -> Vec { - let mut rdr = csv::Reader::from_reader(data.as_bytes()); - let mut list = vec![]; - for result in rdr.deserialize() { - let record: PromptRecord = result.unwrap_or_else(|err| { - info!("parse_prompt_error: {}", err); - PromptRecord { - cmd: None, - act: "".to_string(), - prompt: "".to_string(), - } - }); - if !record.act.is_empty() { - list.push(record); - } + let mut rdr = csv::Reader::from_reader(data.as_bytes()); + let mut list = vec![]; + for result in rdr.deserialize() { + let record: PromptRecord = result.unwrap_or_else(|err| { + info!("parse_prompt_error: {}", err); + PromptRecord { + cmd: None, + act: "".to_string(), + prompt: "".to_string(), + } + }); + if !record.act.is_empty() { + list.push(record); } - list + } + list } #[command] pub fn window_reload(app: AppHandle, label: &str) { - app.app_handle() - .get_window(label) - .unwrap() - .eval("window.location.reload()") - .unwrap(); + app + .app_handle() + .get_window(label) + .unwrap() + .eval("window.location.reload()") + .unwrap(); } #[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] pub struct ModelRecord { - pub cmd: String, - pub act: String, - pub prompt: String, - pub tags: Vec, - pub enable: bool, + pub cmd: String, + pub act: String, + pub prompt: String, + pub tags: Vec, + pub enable: bool, } #[command] pub fn cmd_list() -> Vec { - let mut list = vec![]; - for entry in WalkDir::new(utils::chat_root().join("cache_model")) - .into_iter() - .filter_map(|e| e.ok()) - { - let file = fs::read_to_string(entry.path().display().to_string()); - if let Ok(v) = file { - let data: Vec = serde_json::from_str(&v).unwrap_or_else(|_| vec![]); - let enable_list = data.into_iter().filter(|v| v.enable); - list.extend(enable_list) - } + let mut list = vec![]; + for entry in WalkDir::new(utils::chat_root().join("cache_model")) + .into_iter() + .filter_map(|e| e.ok()) + { + let file = fs::read_to_string(entry.path().display().to_string()); + if let Ok(v) = file { + let data: Vec = serde_json::from_str(&v).unwrap_or_else(|_| vec![]); + let enable_list = data.into_iter().filter(|v| v.enable); + list.extend(enable_list) } - // dbg!(&list); - list.sort_by(|a, b| a.cmd.len().cmp(&b.cmd.len())); - list + } + // dbg!(&list); + list.sort_by(|a, b| a.cmd.len().cmp(&b.cmd.len())); + list } #[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] pub struct FileMetadata { - pub name: String, - pub ext: String, - pub created: u64, - pub id: String, + pub name: String, + pub ext: String, + pub created: u64, + pub id: String, } #[tauri::command] pub fn get_download_list(pathname: &str) -> (Vec, PathBuf) { - info!("get_download_list: {}", pathname); - let download_path = chat_root().join(PathBuf::from(pathname)); - let content = fs::read_to_string(&download_path).unwrap_or_else(|err| { - info!("download_list_error: {}", err); - fs::write(&download_path, "[]").unwrap(); - "[]".to_string() - }); - let list = serde_json::from_str::>(&content).unwrap_or_else(|err| { - info!("download_list_parse_error: {}", err); - vec![] - }); + info!("get_download_list: {}", pathname); + let download_path = chat_root().join(PathBuf::from(pathname)); + let content = fs::read_to_string(&download_path).unwrap_or_else(|err| { + info!("download_list_error: {}", err); + fs::write(&download_path, "[]").unwrap(); + "[]".to_string() + }); + let list = serde_json::from_str::>(&content).unwrap_or_else(|err| { + info!("download_list_parse_error: {}", err); + vec![] + }); - (list, download_path) + (list, download_path) } #[command] pub fn download_list(pathname: &str, dir: &str, filename: Option, id: Option) { - info!("download_list: {}", pathname); - let data = get_download_list(pathname); - let mut list = vec![]; - let mut idmap = HashMap::new(); - utils::vec_to_hashmap(data.0.into_iter(), "id", &mut idmap); + info!("download_list: {}", pathname); + let data = get_download_list(pathname); + let mut list = vec![]; + 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)) - .into_iter() - .filter_entry(|e| !utils::is_hidden(e)) - .filter_map(|e| e.ok()) - { - let metadata = entry.metadata().unwrap(); - if metadata.is_file() { - let file_path = entry.path().display().to_string(); - let re = Regex::new(r"(?P[\d\w]+).(?P\w+)$").unwrap(); - let caps = re.captures(&file_path).unwrap(); - let fid = &caps["id"]; - let fext = &caps["ext"]; + for entry in WalkDir::new(utils::chat_root().join(dir)) + .into_iter() + .filter_entry(|e| !utils::is_hidden(e)) + .filter_map(|e| e.ok()) + { + let metadata = entry.metadata().unwrap(); + if metadata.is_file() { + let file_path = entry.path().display().to_string(); + let re = Regex::new(r"(?P[\d\w]+).(?P\w+)$").unwrap(); + let caps = re.captures(&file_path).unwrap(); + let fid = &caps["id"]; + let fext = &caps["ext"]; - let mut file_data = FileMetadata { - name: fid.to_string(), - id: fid.to_string(), - ext: fext.to_string(), - created: fs_extra::system_time_to_ms(metadata.created()), - }; + let mut file_data = FileMetadata { + name: fid.to_string(), + id: fid.to_string(), + ext: fext.to_string(), + created: fs_extra::system_time_to_ms(metadata.created()), + }; - if idmap.get(fid).is_some() { - let name = idmap.get(fid).unwrap().get("name").unwrap().clone(); - match name { - serde_json::Value::String(v) => { - file_data.name = v.clone(); - v - } - _ => "".to_string(), - }; + if idmap.get(fid).is_some() { + let name = idmap.get(fid).unwrap().get("name").unwrap().clone(); + match name { + serde_json::Value::String(v) => { + file_data.name = v.clone(); + v + } + _ => "".to_string(), + }; + } + + if filename.is_some() && id.is_some() { + if let Some(ref v) = id { + if fid == v { + if let Some(ref v2) = filename { + file_data.name = v2.to_string(); } - - if filename.is_some() && id.is_some() { - if let Some(ref v) = id { - if fid == v { - if let Some(ref v2) = filename { - file_data.name = v2.to_string(); - } - } - } - } - list.push(serde_json::to_value(file_data).unwrap()); + } } + } + list.push(serde_json::to_value(file_data).unwrap()); } + } - // dbg!(&list); - list.sort_by(|a, b| { - let a1 = a.get("created").unwrap().as_u64().unwrap(); - let b1 = b.get("created").unwrap().as_u64().unwrap(); - a1.cmp(&b1).reverse() - }); + // dbg!(&list); + list.sort_by(|a, b| { + let a1 = a.get("created").unwrap().as_u64().unwrap(); + let b1 = b.get("created").unwrap().as_u64().unwrap(); + a1.cmp(&b1).reverse() + }); - fs::write(data.1, serde_json::to_string_pretty(&list).unwrap()).unwrap(); + fs::write(data.1, serde_json::to_string_pretty(&list).unwrap()).unwrap(); } #[command] pub async fn sync_prompts(app: AppHandle, time: u64) -> Option> { - let res = utils::get_data(GITHUB_PROMPTS_CSV_URL, Some(&app)) - .await - .unwrap(); + let res = utils::get_data(GITHUB_PROMPTS_CSV_URL, Some(&app)).await.unwrap(); - if let Some(v) = res { - let data = parse_prompt(v) - .iter() - .map(move |i| ModelRecord { - cmd: if i.cmd.is_some() { - i.cmd.clone().unwrap() - } else { - utils::gen_cmd(i.act.clone()) - }, - act: i.act.clone(), - prompt: i.prompt.clone(), - tags: vec!["chatgpt-prompts".to_string()], - enable: true, - }) - .collect::>(); + if let Some(v) = res { + let data = parse_prompt(v) + .iter() + .map(move |i| ModelRecord { + cmd: if i.cmd.is_some() { + i.cmd.clone().unwrap() + } else { + utils::gen_cmd(i.act.clone()) + }, + act: i.act.clone(), + prompt: i.prompt.clone(), + tags: vec!["chatgpt-prompts".to_string()], + enable: true, + }) + .collect::>(); - let data2 = data.clone(); + 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() - .join("cache_model") - .join("chatgpt_prompts.json"); + 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().join("cache_model").join("chatgpt_prompts.json"); - if !utils::exists(&model) { - fs::write( - &model, - serde_json::json!({ - "name": "ChatGPT Model", - "link": "https://github.com/lencx/ChatGPT" - }) - .to_string(), - ) - .unwrap(); - } - - // chatgpt_prompts.json - fs::write( - chatgpt_prompts, - serde_json::to_string_pretty(&data).unwrap(), - ) - .unwrap(); - let cmd_data = cmd_list(); - - // chat.model.cmd.json - fs::write( - model_cmd, - serde_json::to_string_pretty(&serde_json::json!({ - "name": "ChatGPT CMD", - "last_updated": time, - "data": cmd_data, - })) - .unwrap(), - ) - .unwrap(); - let mut kv = HashMap::new(); - kv.insert( - "sync_prompts".to_string(), - serde_json::json!({ "id": "chatgpt_prompts", "last_updated": time }), - ); - let model_data = utils::merge( - &serde_json::from_str(&fs::read_to_string(&model).unwrap()).unwrap(), - &kv, - ); - - // chat.model.json - fs::write(model, serde_json::to_string_pretty(&model_data).unwrap()).unwrap(); - - // refresh window - api::dialog::message( - app.get_window("core").as_ref(), - "Sync Prompts", - "ChatGPT Prompts data has been synchronized!", - ); - window_reload(app.clone(), "core"); - window_reload(app, "tray"); - - return Some(data2); + if !utils::exists(&model) { + fs::write( + &model, + serde_json::json!({ + "name": "ChatGPT Model", + "link": "https://github.com/lencx/ChatGPT" + }) + .to_string(), + ) + .unwrap(); } - None + // chatgpt_prompts.json + fs::write(chatgpt_prompts, serde_json::to_string_pretty(&data).unwrap()).unwrap(); + let cmd_data = cmd_list(); + + // chat.model.cmd.json + fs::write( + model_cmd, + serde_json::to_string_pretty(&serde_json::json!({ + "name": "ChatGPT CMD", + "last_updated": time, + "data": cmd_data, + })) + .unwrap(), + ) + .unwrap(); + let mut kv = HashMap::new(); + kv.insert( + "sync_prompts".to_string(), + serde_json::json!({ "id": "chatgpt_prompts", "last_updated": time }), + ); + let model_data = utils::merge( + &serde_json::from_str(&fs::read_to_string(&model).unwrap()).unwrap(), + &kv, + ); + + // chat.model.json + fs::write(model, serde_json::to_string_pretty(&model_data).unwrap()).unwrap(); + + // refresh window + api::dialog::message( + app.get_window("core").as_ref(), + "Sync Prompts", + "ChatGPT Prompts data has been synchronized!", + ); + window_reload(app.clone(), "core"); + window_reload(app, "tray"); + + return Some(data2); + } + + None } #[command] pub async fn sync_user_prompts(url: String, data_type: String) -> Option> { - let res = utils::get_data(&url, None).await.unwrap_or_else(|err| { - info!("chatgpt_http_error: {}", err); - None - }); + let res = utils::get_data(&url, None).await.unwrap_or_else(|err| { + info!("chatgpt_http_error: {}", err); + None + }); - info!("chatgpt_http_url: {}", url); + info!("chatgpt_http_url: {}", url); - if let Some(v) = res { - let data; - if data_type == "csv" { - info!("chatgpt_http_csv_parse"); - data = parse_prompt(v); - } 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); - vec![] - }); - } else { - info!("chatgpt_http_unknown_type"); - data = vec![]; - } - - let data = data - .iter() - .map(move |i| ModelRecord { - cmd: if i.cmd.is_some() { - i.cmd.clone().unwrap() - } else { - utils::gen_cmd(i.act.clone()) - }, - act: i.act.clone(), - prompt: i.prompt.clone(), - tags: vec!["user-sync".to_string()], - enable: true, - }) - .collect::>(); - - return Some(data); + if let Some(v) = res { + let data; + if data_type == "csv" { + info!("chatgpt_http_csv_parse"); + data = parse_prompt(v); + } 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); + vec![] + }); + } else { + info!("chatgpt_http_unknown_type"); + data = vec![]; } - None + let data = data + .iter() + .map(move |i| ModelRecord { + cmd: if i.cmd.is_some() { + i.cmd.clone().unwrap() + } else { + utils::gen_cmd(i.act.clone()) + }, + act: i.act.clone(), + prompt: i.prompt.clone(), + tags: vec!["user-sync".to_string()], + enable: true, + }) + .collect::>(); + + return Some(data); + } + + None } diff --git a/src-tauri/src/app/fs_extra.rs b/src-tauri/src/app/fs_extra.rs index 9ed9cf0..40b3c09 100644 --- a/src-tauri/src/app/fs_extra.rs +++ b/src-tauri/src/app/fs_extra.rs @@ -6,8 +6,8 @@ use serde::{ser::Serializer, Serialize}; use std::{ - path::PathBuf, - time::{SystemTime, UNIX_EPOCH}, + path::PathBuf, + time::{SystemTime, UNIX_EPOCH}, }; use tauri::command; @@ -20,101 +20,102 @@ type Result = std::result::Result; #[derive(Debug, thiserror::Error)] pub enum Error { - #[error(transparent)] - Io(#[from] std::io::Error), + #[error(transparent)] + Io(#[from] std::io::Error), } impl Serialize for Error { - fn serialize(&self, serializer: S) -> std::result::Result - where - S: Serializer, - { - serializer.serialize_str(self.to_string().as_ref()) - } + fn serialize(&self, serializer: S) -> std::result::Result + where + S: Serializer, + { + serializer.serialize_str(self.to_string().as_ref()) + } } #[derive(Serialize)] #[serde(rename_all = "camelCase")] struct Permissions { - readonly: bool, - #[cfg(unix)] - mode: u32, + readonly: bool, + #[cfg(unix)] + mode: u32, } #[cfg(unix)] #[derive(Serialize)] #[serde(rename_all = "camelCase")] struct UnixMetadata { - dev: u64, - ino: u64, - mode: u32, - nlink: u64, - uid: u32, - gid: u32, - rdev: u64, - blksize: u64, - blocks: u64, + dev: u64, + ino: u64, + mode: u32, + nlink: u64, + uid: u32, + gid: u32, + rdev: u64, + blksize: u64, + blocks: u64, } #[derive(Serialize)] #[serde(rename_all = "camelCase")] pub struct Metadata { - accessed_at_ms: u64, - pub created_at_ms: u64, - modified_at_ms: u64, - is_dir: bool, - is_file: bool, - is_symlink: bool, - size: u64, - permissions: Permissions, - #[cfg(unix)] - #[serde(flatten)] - unix: UnixMetadata, - #[cfg(windows)] - file_attributes: u32, + accessed_at_ms: u64, + pub created_at_ms: u64, + modified_at_ms: u64, + is_dir: bool, + is_file: bool, + is_symlink: bool, + size: u64, + permissions: Permissions, + #[cfg(unix)] + #[serde(flatten)] + unix: UnixMetadata, + #[cfg(windows)] + file_attributes: u32, } pub fn system_time_to_ms(time: std::io::Result) -> u64 { - time.map(|t| { - let duration_since_epoch = t.duration_since(UNIX_EPOCH).unwrap(); - duration_since_epoch.as_millis() as u64 + time + .map(|t| { + let duration_since_epoch = t.duration_since(UNIX_EPOCH).unwrap(); + duration_since_epoch.as_millis() as u64 }) .unwrap_or_default() } #[command] pub async fn metadata(path: PathBuf) -> Result { - let metadata = std::fs::metadata(path)?; - let file_type = metadata.file_type(); - let permissions = metadata.permissions(); - Ok(Metadata { - accessed_at_ms: system_time_to_ms(metadata.accessed()), - created_at_ms: system_time_to_ms(metadata.created()), - modified_at_ms: system_time_to_ms(metadata.modified()), - is_dir: file_type.is_dir(), - is_file: file_type.is_file(), - is_symlink: file_type.is_symlink(), - size: metadata.len(), - permissions: Permissions { - readonly: permissions.readonly(), - #[cfg(unix)] - mode: permissions.mode(), - }, - #[cfg(unix)] - unix: UnixMetadata { - dev: metadata.dev(), - ino: metadata.ino(), - mode: metadata.mode(), - nlink: metadata.nlink(), - uid: metadata.uid(), - gid: metadata.gid(), - rdev: metadata.rdev(), - blksize: metadata.blksize(), - blocks: metadata.blocks(), - }, - #[cfg(windows)] - file_attributes: metadata.file_attributes(), - }) + let metadata = std::fs::metadata(path)?; + let file_type = metadata.file_type(); + let permissions = metadata.permissions(); + Ok(Metadata { + accessed_at_ms: system_time_to_ms(metadata.accessed()), + created_at_ms: system_time_to_ms(metadata.created()), + modified_at_ms: system_time_to_ms(metadata.modified()), + is_dir: file_type.is_dir(), + is_file: file_type.is_file(), + is_symlink: file_type.is_symlink(), + size: metadata.len(), + permissions: Permissions { + readonly: permissions.readonly(), + #[cfg(unix)] + mode: permissions.mode(), + }, + #[cfg(unix)] + unix: UnixMetadata { + dev: metadata.dev(), + ino: metadata.ino(), + mode: metadata.mode(), + nlink: metadata.nlink(), + uid: metadata.uid(), + gid: metadata.gid(), + rdev: metadata.rdev(), + blksize: metadata.blksize(), + blocks: metadata.blocks(), + }, + #[cfg(windows)] + file_attributes: metadata.file_attributes(), + }) } // #[command] diff --git a/src-tauri/src/app/menu.rs b/src-tauri/src/app/menu.rs index 1a0faf7..25a3789 100644 --- a/src-tauri/src/app/menu.rs +++ b/src-tauri/src/app/menu.rs @@ -1,11 +1,11 @@ use crate::{ - app::{cmd, window}, - conf::{self, ChatConfJson}, - utils, + app::{cmd, window}, + conf::{self, ChatConfJson}, + utils, }; use tauri::{ - AppHandle, CustomMenuItem, Manager, Menu, MenuItem, Submenu, SystemTray, SystemTrayEvent, - SystemTrayMenu, SystemTrayMenuItem, WindowMenuEvent, + AppHandle, CustomMenuItem, Manager, Menu, MenuItem, Submenu, SystemTray, SystemTrayEvent, SystemTrayMenu, + SystemTrayMenuItem, WindowMenuEvent, }; use tauri_plugin_positioner::{on_tray_event, Position, WindowExt}; @@ -14,461 +14,404 @@ use tauri::AboutMetadata; // --- Menu pub fn init() -> Menu { - let chat_conf = ChatConfJson::get_chat_conf(); - let name = "ChatGPT"; - let app_menu = Submenu::new( - name, - Menu::with_items([ - #[cfg(target_os = "macos")] - MenuItem::About(name.into(), AboutMetadata::default()).into(), - #[cfg(not(target_os = "macos"))] - CustomMenuItem::new("about".to_string(), "About ChatGPT").into(), - CustomMenuItem::new("check_update".to_string(), "Check for Updates").into(), - MenuItem::Services.into(), - MenuItem::Hide.into(), - MenuItem::HideOthers.into(), - MenuItem::ShowAll.into(), - MenuItem::Separator.into(), - MenuItem::Quit.into(), - ]), - ); + let chat_conf = ChatConfJson::get_chat_conf(); + let name = "ChatGPT"; + let app_menu = Submenu::new( + name, + Menu::with_items([ + #[cfg(target_os = "macos")] + MenuItem::About(name.into(), AboutMetadata::default()).into(), + #[cfg(not(target_os = "macos"))] + CustomMenuItem::new("about".to_string(), "About ChatGPT").into(), + CustomMenuItem::new("check_update".to_string(), "Check for Updates").into(), + MenuItem::Services.into(), + MenuItem::Hide.into(), + MenuItem::HideOthers.into(), + MenuItem::ShowAll.into(), + MenuItem::Separator.into(), + MenuItem::Quit.into(), + ]), + ); - 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 { - stay_on_top.selected() - } else { - stay_on_top - }; + 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 { + stay_on_top.selected() + } else { + stay_on_top + }; - let theme_light = CustomMenuItem::new("theme_light".to_string(), "Light"); - let theme_dark = CustomMenuItem::new("theme_dark".to_string(), "Dark"); - let theme_system = CustomMenuItem::new("theme_system".to_string(), "System"); - let is_dark = chat_conf.theme == "Dark"; - let is_system = chat_conf.theme == "System"; + 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 update_prompt = CustomMenuItem::new("update_prompt".to_string(), "Prompt"); - let update_silent = CustomMenuItem::new("update_silent".to_string(), "Silent"); - let _update_disable = CustomMenuItem::new("update_disable".to_string(), "Disable"); + let update_prompt = CustomMenuItem::new("update_prompt".to_string(), "Prompt"); + let update_silent = CustomMenuItem::new("update_silent".to_string(), "Silent"); + 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 { - popup_search.selected() - } else { - popup_search - }; + let popup_search = CustomMenuItem::new("popup_search".to_string(), "Pop-up Search"); + let popup_search_menu = if chat_conf.popup_search { + popup_search.selected() + } else { + popup_search + }; - #[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 { - titlebar.selected() - } else { - titlebar - }; + #[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 { + titlebar.selected() + } else { + titlebar + }; - let system_tray = CustomMenuItem::new("system_tray".to_string(), "System Tray"); - let system_tray_menu = if chat_conf.tray { - system_tray.selected() - } else { - system_tray - }; + let system_tray = CustomMenuItem::new("system_tray".to_string(), "System Tray"); + let system_tray_menu = if chat_conf.tray { + system_tray.selected() + } else { + system_tray + }; - let preferences_menu = Submenu::new( - "Preferences", - Menu::with_items([ - CustomMenuItem::new("control_center".to_string(), "Control Center") - .accelerator("CmdOrCtrl+Shift+P") - .into(), - MenuItem::Separator.into(), - stay_on_top_menu.into(), - #[cfg(target_os = "macos")] - titlebar_menu.into(), - #[cfg(target_os = "macos")] - CustomMenuItem::new("hide_dock_icon".to_string(), "Hide Dock Icon").into(), - system_tray_menu.into(), - CustomMenuItem::new("inject_script".to_string(), "Inject Script") - .accelerator("CmdOrCtrl+J") - .into(), - MenuItem::Separator.into(), - Submenu::new( - "Theme", - Menu::new() - .add_item(if is_dark || is_system { - theme_light - } else { - theme_light.selected() - }) - .add_item(if is_dark { - theme_dark.selected() - } else { - theme_dark - }) - .add_item(if is_system { - theme_system.selected() - } else { - theme_system - }), - ) - .into(), - Submenu::new( - "Auto Update", - Menu::new() - .add_item(if chat_conf.auto_update == "Prompt" { - update_prompt.selected() - } else { - update_prompt - }) - .add_item(if chat_conf.auto_update == "Silent" { - update_silent.selected() - } else { - update_silent - }), // .add_item(if chat_conf.auto_update == "Disable" { - // update_disable.selected() - // } else { - // update_disable - // }) - ) - .into(), - MenuItem::Separator.into(), - popup_search_menu.into(), - CustomMenuItem::new("sync_prompts".to_string(), "Sync Prompts").into(), - MenuItem::Separator.into(), - CustomMenuItem::new("go_conf".to_string(), "Go to Config") - .accelerator("CmdOrCtrl+Shift+G") - .into(), - CustomMenuItem::new("clear_conf".to_string(), "Clear Config") - .accelerator("CmdOrCtrl+Shift+D") - .into(), - CustomMenuItem::new("restart".to_string(), "Restart ChatGPT") - .accelerator("CmdOrCtrl+Shift+R") - .into(), - MenuItem::Separator.into(), - CustomMenuItem::new("awesome".to_string(), "Awesome ChatGPT") - .accelerator("CmdOrCtrl+Shift+A") - .into(), - CustomMenuItem::new("buy_coffee".to_string(), "Buy lencx a coffee").into(), - ]), - ); - - let edit_menu = Submenu::new( - "Edit", + let preferences_menu = Submenu::new( + "Preferences", + Menu::with_items([ + CustomMenuItem::new("control_center".to_string(), "Control Center") + .accelerator("CmdOrCtrl+Shift+P") + .into(), + MenuItem::Separator.into(), + stay_on_top_menu.into(), + #[cfg(target_os = "macos")] + titlebar_menu.into(), + #[cfg(target_os = "macos")] + CustomMenuItem::new("hide_dock_icon".to_string(), "Hide Dock Icon").into(), + system_tray_menu.into(), + CustomMenuItem::new("inject_script".to_string(), "Inject Script") + .accelerator("CmdOrCtrl+J") + .into(), + MenuItem::Separator.into(), + Submenu::new( + "Theme", Menu::new() - .add_native_item(MenuItem::Undo) - .add_native_item(MenuItem::Redo) - .add_native_item(MenuItem::Separator) - .add_native_item(MenuItem::Cut) - .add_native_item(MenuItem::Copy) - .add_native_item(MenuItem::Paste) - .add_native_item(MenuItem::SelectAll), - ); - - let view_menu = Submenu::new( - "View", + .add_item(if is_dark || is_system { + theme_light + } else { + theme_light.selected() + }) + .add_item(if is_dark { theme_dark.selected() } else { theme_dark }) + .add_item(if is_system { + theme_system.selected() + } else { + theme_system + }), + ) + .into(), + Submenu::new( + "Auto Update", Menu::new() - .add_item( - CustomMenuItem::new("go_back".to_string(), "Go Back").accelerator("CmdOrCtrl+Left"), - ) - .add_item( - CustomMenuItem::new("go_forward".to_string(), "Go Forward") - .accelerator("CmdOrCtrl+Right"), - ) - .add_item( - CustomMenuItem::new("scroll_top".to_string(), "Scroll to Top of Screen") - .accelerator("CmdOrCtrl+Up"), - ) - .add_item( - CustomMenuItem::new("scroll_bottom".to_string(), "Scroll to Bottom of Screen") - .accelerator("CmdOrCtrl+Down"), - ) - .add_native_item(MenuItem::Separator) - .add_item( - CustomMenuItem::new("reload".to_string(), "Refresh the Screen") - .accelerator("CmdOrCtrl+R"), - ), - ); - - let window_menu = Submenu::new( - "Window", - Menu::new() - .add_item(CustomMenuItem::new("dalle2".to_string(), "DALL·E 2")) - .add_native_item(MenuItem::Separator) - .add_native_item(MenuItem::Minimize) - .add_native_item(MenuItem::Zoom), - ); - - let help_menu = Submenu::new( - "Help", - Menu::new() - .add_item(CustomMenuItem::new( - "chatgpt_log".to_string(), - "ChatGPT Log", - )) - .add_item(CustomMenuItem::new("update_log".to_string(), "Update Log")) - .add_item(CustomMenuItem::new("report_bug".to_string(), "Report Bug")) - .add_item( - CustomMenuItem::new("dev_tools".to_string(), "Toggle Developer Tools") - .accelerator("CmdOrCtrl+Shift+I"), - ), - ); + .add_item(if chat_conf.auto_update == "Prompt" { + update_prompt.selected() + } else { + update_prompt + }) + .add_item(if chat_conf.auto_update == "Silent" { + update_silent.selected() + } else { + update_silent + }), // .add_item(if chat_conf.auto_update == "Disable" { + // update_disable.selected() + // } else { + // update_disable + // }) + ) + .into(), + MenuItem::Separator.into(), + popup_search_menu.into(), + CustomMenuItem::new("sync_prompts".to_string(), "Sync Prompts").into(), + MenuItem::Separator.into(), + CustomMenuItem::new("go_conf".to_string(), "Go to Config") + .accelerator("CmdOrCtrl+Shift+G") + .into(), + CustomMenuItem::new("clear_conf".to_string(), "Clear Config") + .accelerator("CmdOrCtrl+Shift+D") + .into(), + CustomMenuItem::new("restart".to_string(), "Restart ChatGPT") + .accelerator("CmdOrCtrl+Shift+R") + .into(), + MenuItem::Separator.into(), + CustomMenuItem::new("awesome".to_string(), "Awesome ChatGPT") + .accelerator("CmdOrCtrl+Shift+A") + .into(), + CustomMenuItem::new("buy_coffee".to_string(), "Buy lencx a coffee").into(), + ]), + ); + let edit_menu = Submenu::new( + "Edit", Menu::new() - .add_submenu(app_menu) - .add_submenu(preferences_menu) - .add_submenu(window_menu) - .add_submenu(edit_menu) - .add_submenu(view_menu) - .add_submenu(help_menu) + .add_native_item(MenuItem::Undo) + .add_native_item(MenuItem::Redo) + .add_native_item(MenuItem::Separator) + .add_native_item(MenuItem::Cut) + .add_native_item(MenuItem::Copy) + .add_native_item(MenuItem::Paste) + .add_native_item(MenuItem::SelectAll), + ); + + 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_forward".to_string(), "Go Forward").accelerator("CmdOrCtrl+Right")) + .add_item(CustomMenuItem::new("scroll_top".to_string(), "Scroll to Top of Screen").accelerator("CmdOrCtrl+Up")) + .add_item( + CustomMenuItem::new("scroll_bottom".to_string(), "Scroll to Bottom of Screen").accelerator("CmdOrCtrl+Down"), + ) + .add_native_item(MenuItem::Separator) + .add_item(CustomMenuItem::new("reload".to_string(), "Refresh the Screen").accelerator("CmdOrCtrl+R")), + ); + + let window_menu = Submenu::new( + "Window", + Menu::new() + .add_item(CustomMenuItem::new("dalle2".to_string(), "DALL·E 2")) + .add_native_item(MenuItem::Separator) + .add_native_item(MenuItem::Minimize) + .add_native_item(MenuItem::Zoom), + ); + + let help_menu = Submenu::new( + "Help", + Menu::new() + .add_item(CustomMenuItem::new("chatgpt_log".to_string(), "ChatGPT Log")) + .add_item(CustomMenuItem::new("update_log".to_string(), "Update Log")) + .add_item(CustomMenuItem::new("report_bug".to_string(), "Report Bug")) + .add_item( + CustomMenuItem::new("dev_tools".to_string(), "Toggle Developer Tools").accelerator("CmdOrCtrl+Shift+I"), + ), + ); + + Menu::new() + .add_submenu(app_menu) + .add_submenu(preferences_menu) + .add_submenu(window_menu) + .add_submenu(edit_menu) + .add_submenu(view_menu) + .add_submenu(help_menu) } // --- Menu Event pub fn menu_handler(event: WindowMenuEvent) { - let win = Some(event.window()).unwrap(); - let app = win.app_handle(); - let script_path = utils::script_path().to_string_lossy().to_string(); - let menu_id = event.menu_item_id(); - let menu_handle = win.menu_handle(); + let win = Some(event.window()).unwrap(); + let app = win.app_handle(); + let script_path = utils::script_path().to_string_lossy().to_string(); + let menu_id = event.menu_item_id(); + let menu_handle = win.menu_handle(); - match menu_id { - // App - "about" => { - let tauri_conf = utils::get_tauri_conf().unwrap(); - tauri::api::dialog::message( - app.get_window("core").as_ref(), - "ChatGPT", - format!("Version {}", tauri_conf.package.version.unwrap()), - ); + match menu_id { + // App + "about" => { + let tauri_conf = utils::get_tauri_conf().unwrap(); + tauri::api::dialog::message( + app.get_window("core").as_ref(), + "ChatGPT", + format!("Version {}", tauri_conf.package.version.unwrap()), + ); + } + "check_update" => { + utils::run_check_update(app, false, None); + } + // Preferences + "control_center" => window::control_window(&app), + "restart" => tauri::api::process::restart(&app.env()), + "inject_script" => open(&app, script_path), + "go_conf" => utils::open_file(utils::chat_root()), + "clear_conf" => utils::clear_conf(&app), + "awesome" => open(&app, conf::AWESOME_URL.to_string()), + "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; + menu_handle.get_item(menu_id).set_selected(popup_search).unwrap(); + ChatConfJson::amend(&serde_json::json!({ "popup_search": popup_search }), None).unwrap(); + cmd::window_reload(app.clone(), "core"); + cmd::window_reload(app, "tray"); + } + "sync_prompts" => { + tauri::api::dialog::ask( + app.get_window("core").as_ref(), + "Sync Prompts", + "Data sync will enable all prompts, are you sure you want to sync?", + move |is_restart| { + if is_restart { + app + .get_window("core") + .unwrap() + .eval("window.__sync_prompts && window.__sync_prompts()") + .unwrap() + } + }, + ); + } + "hide_dock_icon" => ChatConfJson::amend(&serde_json::json!({ "hide_dock_icon": true }), Some(app)).unwrap(), + "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()); + } + "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()); + } + "theme_light" | "theme_dark" | "theme_system" => { + let theme = match menu_id { + "theme_dark" => "Dark", + "theme_system" => "System", + _ => "Light", + }; + ChatConfJson::amend(&serde_json::json!({ "theme": theme }), Some(app)).unwrap(); + } + "update_prompt" | "update_silent" | "update_disable" => { + // for id in ["update_prompt", "update_silent", "update_disable"] { + for id in ["update_prompt", "update_silent"] { + menu_handle.get_item(id).set_selected(false).unwrap(); + } + let auto_update = match menu_id { + "update_silent" => { + menu_handle.get_item("update_silent").set_selected(true).unwrap(); + "Silent" } - "check_update" => { - utils::run_check_update(app, false, None); + "update_disable" => { + menu_handle.get_item("update_disable").set_selected(true).unwrap(); + "Disable" } - // Preferences - "control_center" => window::control_window(&app), - "restart" => tauri::api::process::restart(&app.env()), - "inject_script" => open(&app, script_path), - "go_conf" => utils::open_file(utils::chat_root()), - "clear_conf" => utils::clear_conf(&app), - "awesome" => open(&app, conf::AWESOME_URL.to_string()), - "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; - menu_handle - .get_item(menu_id) - .set_selected(popup_search) - .unwrap(); - ChatConfJson::amend(&serde_json::json!({ "popup_search": popup_search }), None) - .unwrap(); - cmd::window_reload(app.clone(), "core"); - cmd::window_reload(app, "tray"); + _ => { + menu_handle.get_item("update_prompt").set_selected(true).unwrap(); + "Prompt" } - "sync_prompts" => { - tauri::api::dialog::ask( - app.get_window("core").as_ref(), - "Sync Prompts", - "Data sync will enable all prompts, are you sure you want to sync?", - move |is_restart| { - if is_restart { - app.get_window("core") - .unwrap() - .eval("window.__sync_prompts && window.__sync_prompts()") - .unwrap() - } - }, - ); - } - "hide_dock_icon" => { - ChatConfJson::amend(&serde_json::json!({ "hide_dock_icon": true }), Some(app)).unwrap() - } - "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()); - } - "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()); - } - "theme_light" | "theme_dark" | "theme_system" => { - let theme = match menu_id { - "theme_dark" => "Dark", - "theme_system" => "System", - _ => "Light", - }; - ChatConfJson::amend(&serde_json::json!({ "theme": theme }), Some(app)).unwrap(); - } - "update_prompt" | "update_silent" | "update_disable" => { - // for id in ["update_prompt", "update_silent", "update_disable"] { - for id in ["update_prompt", "update_silent"] { - menu_handle.get_item(id).set_selected(false).unwrap(); - } - let auto_update = match menu_id { - "update_silent" => { - menu_handle - .get_item("update_silent") - .set_selected(true) - .unwrap(); - "Silent" - } - "update_disable" => { - menu_handle - .get_item("update_disable") - .set_selected(true) - .unwrap(); - "Disable" - } - _ => { - menu_handle - .get_item("update_prompt") - .set_selected(true) - .unwrap(); - "Prompt" - } - }; - ChatConfJson::amend(&serde_json::json!({ "auto_update": auto_update }), None).unwrap(); - } - "stay_on_top" => { - let chat_conf = conf::ChatConfJson::get_chat_conf(); - let stay_on_top = !chat_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(); - } - // Window - "dalle2" => window::dalle2_window(&app, None, None, Some(false)), - // View - "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(), - // core: document.querySelector('main .overflow-y-auto') - "scroll_top" => win - .eval( - r#"window.scroll({ + }; + ChatConfJson::amend(&serde_json::json!({ "auto_update": auto_update }), None).unwrap(); + } + "stay_on_top" => { + let chat_conf = conf::ChatConfJson::get_chat_conf(); + let stay_on_top = !chat_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(); + } + // Window + "dalle2" => window::dalle2_window(&app, None, None, Some(false)), + // View + "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(), + // core: document.querySelector('main .overflow-y-auto') + "scroll_top" => win + .eval( + r#"window.scroll({ top: 0, left: 0, behavior: "smooth" })"#, - ) - .unwrap(), - "scroll_bottom" => win - .eval( - r#"window.scroll({ + ) + .unwrap(), + "scroll_bottom" => win + .eval( + r#"window.scroll({ top: document.body.scrollHeight, left: 0, behavior: "smooth"})"#, - ) - .unwrap(), - // Help - "chatgpt_log" => utils::open_file(utils::chat_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" => { - win.open_devtools(); - win.close_devtools(); - } - _ => (), + ) + .unwrap(), + // Help + "chatgpt_log" => utils::open_file(utils::chat_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" => { + win.open_devtools(); + win.close_devtools(); } + _ => (), + } } // --- 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", - )) - .add_item(CustomMenuItem::new( - "hide_dock_icon".to_string(), - "Hide Dock Icon", - )) - .add_item(CustomMenuItem::new("show_core".to_string(), "Show ChatGPT")) - .add_native_item(SystemTrayMenuItem::Separator) - .add_item(CustomMenuItem::new("quit".to_string(), "Quit ChatGPT")), - ) - } else { - SystemTray::new().with_menu( - SystemTrayMenu::new() - .add_item(CustomMenuItem::new( - "control_center".to_string(), - "Control Center", - )) - .add_item(CustomMenuItem::new("show_core".to_string(), "Show ChatGPT")) - .add_native_item(SystemTrayMenuItem::Separator) - .add_item(CustomMenuItem::new("quit".to_string(), "Quit ChatGPT")), - ) - } + 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")) + .add_item(CustomMenuItem::new("hide_dock_icon".to_string(), "Hide Dock Icon")) + .add_item(CustomMenuItem::new("show_core".to_string(), "Show ChatGPT")) + .add_native_item(SystemTrayMenuItem::Separator) + .add_item(CustomMenuItem::new("quit".to_string(), "Quit ChatGPT")), + ) + } else { + SystemTray::new().with_menu( + SystemTrayMenu::new() + .add_item(CustomMenuItem::new("control_center".to_string(), "Control Center")) + .add_item(CustomMenuItem::new("show_core".to_string(), "Show ChatGPT")) + .add_native_item(SystemTrayMenuItem::Separator) + .add_item(CustomMenuItem::new("quit".to_string(), "Quit ChatGPT")), + ) + } } // --- SystemTray Event pub fn tray_handler(handle: &AppHandle, event: SystemTrayEvent) { - on_tray_event(handle, &event); + on_tray_event(handle, &event); - let app = handle.clone(); + let app = handle.clone(); - match event { - SystemTrayEvent::LeftClick { .. } => { - let chat_conf = conf::ChatConfJson::get_chat_conf(); + match event { + SystemTrayEvent::LeftClick { .. } => { + let chat_conf = conf::ChatConfJson::get_chat_conf(); - if !chat_conf.hide_dock_icon { - let core_win = handle.get_window("core").unwrap(); - core_win.minimize().unwrap(); - } + if !chat_conf.hide_dock_icon { + let core_win = handle.get_window("core").unwrap(); + core_win.minimize().unwrap(); + } - let tray_win = handle.get_window("tray").unwrap(); - tray_win.move_window(Position::TrayCenter).unwrap(); + let tray_win = handle.get_window("tray").unwrap(); + tray_win.move_window(Position::TrayCenter).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), - "restart" => tauri::api::process::restart(&handle.env()), - "show_dock_icon" => { - ChatConfJson::amend(&serde_json::json!({ "hide_dock_icon": false }), Some(app)) - .unwrap(); - } - "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(); - } - } - "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(); - } - } - "quit" => std::process::exit(0), - _ => (), - }, - _ => (), + 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), + "restart" => tauri::api::process::restart(&handle.env()), + "show_dock_icon" => { + ChatConfJson::amend(&serde_json::json!({ "hide_dock_icon": false }), Some(app)).unwrap(); + } + "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(); + } + } + "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(); + } + } + "quit" => std::process::exit(0), + _ => (), + }, + _ => (), + } } pub fn open(app: &AppHandle, path: String) { - tauri::api::shell::open(&app.shell_scope(), path, None).unwrap(); + tauri::api::shell::open(&app.shell_scope(), path, None).unwrap(); } diff --git a/src-tauri/src/app/setup.rs b/src-tauri/src/app/setup.rs index 9c2d5cd..ab2c133 100644 --- a/src-tauri/src/app/setup.rs +++ b/src-tauri/src/app/setup.rs @@ -4,90 +4,90 @@ use tauri::{utils::config::WindowUrl, window::WindowBuilder, App, GlobalShortcut use wry::application::accelerator::Accelerator; pub fn init(app: &mut App) -> std::result::Result<(), Box> { - info!("stepup"); - let chat_conf = ChatConfJson::get_chat_conf(); - let url = chat_conf.origin.to_string(); - let theme = ChatConfJson::theme(); - let handle = app.app_handle(); + info!("stepup"); + let chat_conf = ChatConfJson::get_chat_conf(); + let url = chat_conf.origin.to_string(); + let theme = ChatConfJson::theme(); + let handle = app.app_handle(); + tauri::async_runtime::spawn(async move { + window::tray_window(&handle); + }); + + if let Some(v) = chat_conf.global_shortcut { + info!("global_shortcut: `{}`", v); + match v.parse::() { + Ok(_) => { + info!("global_shortcut_register"); + let handle = app.app_handle(); + let mut shortcut = app.global_shortcut_manager(); + shortcut + .register(&v, move || { + if let Some(w) = handle.get_window("core") { + if w.is_visible().unwrap() { + w.hide().unwrap(); + } else { + w.show().unwrap(); + w.set_focus().unwrap(); + } + } + }) + .unwrap_or_else(|err| { + info!("global_shortcut_register_error: {}", err); + }); + } + Err(err) => { + info!("global_shortcut_parse_error: {}", err); + } + } + } else { + info!("global_shortcut_unregister"); + }; + + if chat_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 { - window::tray_window(&handle); + let mut main_win = WindowBuilder::new(&app, "core", WindowUrl::App(url.into())) + .title("ChatGPT") + .resizable(true) + .fullscreen(false) + .inner_size(800.0, 600.0); + + if cfg!(target_os = "macos") { + main_win = main_win.hidden_title(true); + } + + main_win + .theme(theme) + .always_on_top(chat_conf.stay_on_top) + .title_bar_style(ChatConfJson::titlebar()) + .initialization_script(&utils::user_script()) + .initialization_script(include_str!("../vendors/floating-ui-core.js")) + .initialization_script(include_str!("../vendors/floating-ui-dom.js")) + .initialization_script(include_str!("../vendors/html2canvas.js")) + .initialization_script(include_str!("../vendors/jspdf.js")) + .initialization_script(include_str!("../vendors/turndown.js")) + .initialization_script(include_str!("../vendors/turndown-plugin-gfm.js")) + .initialization_script(include_str!("../scripts/core.js")) + .initialization_script(include_str!("../scripts/popup.core.js")) + .initialization_script(include_str!("../scripts/export.js")) + .initialization_script(include_str!("../scripts/markdown.export.js")) + .initialization_script(include_str!("../scripts/cmd.js")) + .user_agent(&chat_conf.ua_window) + .build() + .unwrap(); }); + } - if let Some(v) = chat_conf.global_shortcut { - info!("global_shortcut: `{}`", v); - match v.parse::() { - Ok(_) => { - info!("global_shortcut_register"); - let handle = app.app_handle(); - let mut shortcut = app.global_shortcut_manager(); - shortcut - .register(&v, move || { - if let Some(w) = handle.get_window("core") { - if w.is_visible().unwrap() { - w.hide().unwrap(); - } else { - w.show().unwrap(); - w.set_focus().unwrap(); - } - } - }) - .unwrap_or_else(|err| { - info!("global_shortcut_register_error: {}", err); - }); - } - Err(err) => { - info!("global_shortcut_parse_error: {}", err); - } - } - } else { - info!("global_shortcut_unregister"); - }; + // auto_update + if chat_conf.auto_update != "Disable" { + info!("stepup::run_check_update"); + let app = app.handle(); + utils::run_check_update(app, chat_conf.auto_update == "Silent", None); + } - if chat_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 mut main_win = WindowBuilder::new(&app, "core", WindowUrl::App(url.into())) - .title("ChatGPT") - .resizable(true) - .fullscreen(false) - .inner_size(800.0, 600.0); - - if cfg!(target_os = "macos") { - main_win = main_win.hidden_title(true); - } - - main_win - .theme(theme) - .always_on_top(chat_conf.stay_on_top) - .title_bar_style(ChatConfJson::titlebar()) - .initialization_script(&utils::user_script()) - .initialization_script(include_str!("../vendors/floating-ui-core.js")) - .initialization_script(include_str!("../vendors/floating-ui-dom.js")) - .initialization_script(include_str!("../vendors/html2canvas.js")) - .initialization_script(include_str!("../vendors/jspdf.js")) - .initialization_script(include_str!("../vendors/turndown.js")) - .initialization_script(include_str!("../vendors/turndown-plugin-gfm.js")) - .initialization_script(include_str!("../scripts/core.js")) - .initialization_script(include_str!("../scripts/popup.core.js")) - .initialization_script(include_str!("../scripts/export.js")) - .initialization_script(include_str!("../scripts/markdown.export.js")) - .initialization_script(include_str!("../scripts/cmd.js")) - .user_agent(&chat_conf.ua_window) - .build() - .unwrap(); - }); - } - - // auto_update - if chat_conf.auto_update != "Disable" { - info!("stepup::run_check_update"); - let app = app.handle(); - utils::run_check_update(app, chat_conf.auto_update == "Silent", None); - } - - Ok(()) + Ok(()) } diff --git a/src-tauri/src/app/window.rs b/src-tauri/src/app/window.rs index d614214..507e633 100644 --- a/src-tauri/src/app/window.rs +++ b/src-tauri/src/app/window.rs @@ -4,104 +4,95 @@ 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 = handle.clone(); + let chat_conf = conf::ChatConfJson::get_chat_conf(); + let theme = conf::ChatConfJson::theme(); + let app = handle.clone(); - tauri::async_runtime::spawn(async move { - WindowBuilder::new(&app, "tray", WindowUrl::App(chat_conf.origin.into())) - .title("ChatGPT") - .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!("../vendors/floating-ui-core.js")) - .initialization_script(include_str!("../vendors/floating-ui-dom.js")) - .initialization_script(include_str!("../scripts/core.js")) - .initialization_script(include_str!("../scripts/cmd.js")) - .initialization_script(include_str!("../scripts/popup.core.js")) - .user_agent(&chat_conf.ua_tray) - .build() - .unwrap() - .hide() - .unwrap(); - }); + tauri::async_runtime::spawn(async move { + WindowBuilder::new(&app, "tray", WindowUrl::App(chat_conf.origin.into())) + .title("ChatGPT") + .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!("../vendors/floating-ui-core.js")) + .initialization_script(include_str!("../vendors/floating-ui-dom.js")) + .initialization_script(include_str!("../scripts/core.js")) + .initialization_script(include_str!("../scripts/cmd.js")) + .initialization_script(include_str!("../scripts/popup.core.js")) + .user_agent(&chat_conf.ua_tray) + .build() + .unwrap() + .hide() + .unwrap(); + }); } -pub fn dalle2_window( - handle: &tauri::AppHandle, - query: Option, - title: Option, - is_new: Option, -) { - info!("dalle2_query: {:?}", query); - let theme = conf::ChatConfJson::theme(); - let app = handle.clone(); +pub fn dalle2_window(handle: &tauri::AppHandle, query: Option, title: Option, is_new: Option) { + info!("dalle2_query: {:?}", query); + let theme = conf::ChatConfJson::theme(); + let app = handle.clone(); - let query = if query.is_some() { - format!( - "window.addEventListener('DOMContentLoaded', function() {{\nwindow.__CHATGPT_QUERY__='{}';\n}})", - query.unwrap() - ) - } else { - "".to_string() - }; + let query = if query.is_some() { + format!( + "window.addEventListener('DOMContentLoaded', function() {{\nwindow.__CHATGPT_QUERY__='{}';\n}})", + query.unwrap() + ) + } else { + "".to_string() + }; - let label = if is_new.unwrap_or(true) { - let timestamp = SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .unwrap() - .as_secs(); - format!("dalle2_{}", timestamp) - } else { - "dalle2".to_string() - }; + let label = if is_new.unwrap_or(true) { + let timestamp = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + .as_secs(); + format!("dalle2_{}", timestamp) + } else { + "dalle2".to_string() + }; - if app.get_window("dalle2").is_none() { - tauri::async_runtime::spawn(async move { - WindowBuilder::new( - &app, - label, - WindowUrl::App("https://labs.openai.com".into()), - ) - .title(title.unwrap_or_else(|| "DALL·E 2".to_string())) - .resizable(true) - .fullscreen(false) - .inner_size(800.0, 600.0) - .always_on_top(false) - .theme(theme) - .initialization_script(include_str!("../scripts/core.js")) - .initialization_script(&query) - .initialization_script(include_str!("../scripts/dalle2.js")) - .build() - .unwrap(); - }); - } else { - let dalle2_win = app.get_window("dalle2").unwrap(); - dalle2_win.show().unwrap(); - dalle2_win.set_focus().unwrap(); - } + if app.get_window("dalle2").is_none() { + tauri::async_runtime::spawn(async move { + WindowBuilder::new(&app, label, WindowUrl::App("https://labs.openai.com".into())) + .title(title.unwrap_or_else(|| "DALL·E 2".to_string())) + .resizable(true) + .fullscreen(false) + .inner_size(800.0, 600.0) + .always_on_top(false) + .theme(theme) + .initialization_script(include_str!("../scripts/core.js")) + .initialization_script(&query) + .initialization_script(include_str!("../scripts/dalle2.js")) + .build() + .unwrap(); + }); + } else { + let dalle2_win = app.get_window("dalle2").unwrap(); + dalle2_win.show().unwrap(); + dalle2_win.set_focus().unwrap(); + } } pub fn control_window(handle: &tauri::AppHandle) { - let app = handle.clone(); - tauri::async_runtime::spawn(async move { - if app.app_handle().get_window("main").is_none() { - WindowBuilder::new(&app, "main", WindowUrl::App("index.html".into())) - .title("Control Center") - .resizable(true) - .fullscreen(false) - .inner_size(1000.0, 700.0) - .min_inner_size(800.0, 600.0) - .build() - .unwrap(); - } else { - let main_win = app.app_handle().get_window("main").unwrap(); - main_win.show().unwrap(); - main_win.set_focus().unwrap(); - } - }); + let app = handle.clone(); + tauri::async_runtime::spawn(async move { + if app.app_handle().get_window("main").is_none() { + WindowBuilder::new(&app, "main", WindowUrl::App("index.html".into())) + .title("Control Center") + .resizable(true) + .fullscreen(false) + .inner_size(1000.0, 700.0) + .min_inner_size(800.0, 600.0) + .build() + .unwrap(); + } else { + let main_win = app.app_handle().get_window("main").unwrap(); + main_win.show().unwrap(); + main_win.set_focus().unwrap(); + } + }); } diff --git a/src-tauri/src/conf.rs b/src-tauri/src/conf.rs index 46d5307..557ca6d 100644 --- a/src-tauri/src/conf.rs +++ b/src-tauri/src/conf.rs @@ -15,8 +15,7 @@ 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 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", @@ -48,153 +47,150 @@ pub const DEFAULT_CHAT_CONF_MAC: &str = r#"{ #[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] pub struct ChatConfJson { - // support macOS only - pub titlebar: bool, - pub hide_dock_icon: bool, + // support macOS only + pub titlebar: bool, + pub hide_dock_icon: bool, - // 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 default_origin: String, - pub origin: String, - pub ua_window: String, - pub ua_tray: String, - pub global_shortcut: Option, + // 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 default_origin: String, + pub origin: String, + pub ua_window: String, + pub ua_tray: String, + pub global_shortcut: Option, } 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 - }; + /// 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 + }; - if !exists(&conf_file) { - create_file(&conf_file).unwrap(); - fs::write(&conf_file, content).unwrap(); - return conf_file; + if !exists(&conf_file) { + create_file(&conf_file).unwrap(); + fs::write(&conf_file, content).unwrap(); + return conf_file; + } + + 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; } + fs::write(&conf_file, content).unwrap(); + } + }; - 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; - } - fs::write(&conf_file, content).unwrap(); - } - }; + conf_file + } - conf_file - } + pub fn conf_path() -> PathBuf { + chat_root().join("chat.conf.json") + } - 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 + }; - 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() - } - } - } - - pub fn reset_chat_conf() -> Self { - let conf_file = ChatConfJson::conf_path(); - 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() + } + } + } + + pub fn reset_chat_conf() -> Self { + let conf_file = ChatConfJson::conf_path(); + let content = if cfg!(target_os = "macos") { + DEFAULT_CHAT_CONF_MAC + } else { + DEFAULT_CHAT_CONF + }; + fs::write(&conf_file, content).unwrap(); + serde_json::from_str(content).unwrap() + } + + // https://users.rust-lang.org/t/updating-object-fields-given-dynamic-json/39049/3 + pub fn amend(new_rules: &Value, app: Option) -> Result<()> { + let config = ChatConfJson::get_chat_conf(); + let config: Value = serde_json::to_value(&config)?; + let mut config: BTreeMap = serde_json::from_value(config)?; + let new_rules: BTreeMap = serde_json::from_value(new_rules.clone())?; + + for (k, v) in new_rules { + config.insert(k, v); } - // https://users.rust-lang.org/t/updating-object-fields-given-dynamic-json/39049/3 - pub fn amend(new_rules: &Value, app: Option) -> Result<()> { - let config = ChatConfJson::get_chat_conf(); - let config: Value = serde_json::to_value(&config)?; - let mut config: BTreeMap = serde_json::from_value(config)?; - let new_rules: BTreeMap = serde_json::from_value(new_rules.clone())?; + fs::write(ChatConfJson::conf_path(), serde_json::to_string_pretty(&config)?)?; - for (k, v) in new_rules { - 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()); - // tauri::api::dialog::ask( - // handle.get_window("core").as_ref(), - // "ChatGPT Restart", - // "Whether to restart immediately?", - // move |is_restart| { - // if is_restart { - // } - // }, - // ); - } - - Ok(()) + if let Some(handle) = app { + tauri::api::process::restart(&handle.env()); + // tauri::api::dialog::ask( + // handle.get_window("core").as_ref(), + // "ChatGPT Restart", + // "Whether to restart immediately?", + // move |is_restart| { + // if is_restart { + // } + // }, + // ); } - pub fn theme() -> Option { - let conf = ChatConfJson::get_chat_conf(); - let theme = match conf.theme.as_str() { - "System" => match dark_light::detect() { - // Dark mode - dark_light::Mode::Dark => Theme::Dark, - // Light mode - dark_light::Mode::Light => Theme::Light, - // Unspecified - dark_light::Mode::Default => Theme::Light, - }, - "Dark" => Theme::Dark, - _ => Theme::Light, - }; + Ok(()) + } - Some(theme) - } + pub fn theme() -> Option { + let conf = ChatConfJson::get_chat_conf(); + let theme = match conf.theme.as_str() { + "System" => match dark_light::detect() { + // Dark mode + dark_light::Mode::Dark => Theme::Dark, + // Light mode + dark_light::Mode::Light => Theme::Light, + // Unspecified + dark_light::Mode::Default => Theme::Light, + }, + "Dark" => Theme::Dark, + _ => Theme::Light, + }; - #[cfg(target_os = "macos")] - pub fn titlebar() -> TitleBarStyle { - let conf = ChatConfJson::get_chat_conf(); - if conf.titlebar { - TitleBarStyle::Transparent - } else { - TitleBarStyle::Overlay - } + Some(theme) + } + + #[cfg(target_os = "macos")] + pub fn titlebar() -> TitleBarStyle { + let conf = ChatConfJson::get_chat_conf(); + if conf.titlebar { + TitleBarStyle::Transparent + } else { + TitleBarStyle::Overlay } + } } diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index bed964b..40b9072 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -1,7 +1,4 @@ -#![cfg_attr( - all(not(debug_assertions), target_os = "windows"), - windows_subsystem = "windows" -)] +#![cfg_attr(all(not(debug_assertions), target_os = "windows"), windows_subsystem = "windows")] mod app; mod conf; @@ -12,104 +9,101 @@ use conf::ChatConfJson; use tauri::api::path; use tauri_plugin_autostart::MacosLauncher; use tauri_plugin_log::{ - fern::colors::{Color, ColoredLevelConfig}, - LogTarget, + fern::colors::{Color, ColoredLevelConfig}, + LogTarget, }; #[tokio::main] async fn main() { - ChatConfJson::init(); - // 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, - }; + ChatConfJson::init(); + // 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, + }; - cmd::download_list("chat.download.json", "download", None, None); - cmd::download_list("chat.notes.json", "notes", None, None); + cmd::download_list("chat.download.json", "download", None, None); + cmd::download_list("chat.notes.json", "notes", None, None); - let chat_conf = ChatConfJson::get_chat_conf(); + let chat_conf = ChatConfJson::get_chat_conf(); - 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(tauri_plugin_positioner::init()) - .plugin(tauri_plugin_autostart::init( - MacosLauncher::LaunchAgent, - None, - )) - .invoke_handler(tauri::generate_handler![ - cmd::drag_window, - cmd::fullscreen, - 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_chat_model_cmd, - cmd::parse_prompt, - cmd::sync_prompts, - cmd::sync_user_prompts, - cmd::window_reload, - cmd::dalle2_window, - cmd::cmd_list, - cmd::download_list, - cmd::get_download_list, - fs_extra::metadata, + 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, ]) - .setup(setup::init) - .menu(menu::init()); + .level(log::LevelFilter::Debug) + .with_colors(colors) + .build(), + ) + .plugin(tauri_plugin_positioner::init()) + .plugin(tauri_plugin_autostart::init(MacosLauncher::LaunchAgent, None)) + .invoke_handler(tauri::generate_handler![ + cmd::drag_window, + cmd::fullscreen, + 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_chat_model_cmd, + cmd::parse_prompt, + cmd::sync_prompts, + cmd::sync_user_prompts, + cmd::window_reload, + cmd::dalle2_window, + cmd::cmd_list, + cmd::download_list, + cmd::get_download_list, + fs_extra::metadata, + ]) + .setup(setup::init) + .menu(menu::init()); - if chat_conf.tray { - builder = builder.system_tray(menu::tray_menu()); - } + if chat_conf.tray { + builder = builder.system_tray(menu::tray_menu()); + } - builder - .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(); + builder + .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(); - } - api.prevent_close(); - } - }) - .run(context) - .expect("error while running ChatGPT application"); + // fix: https://github.com/lencx/ChatGPT/issues/93 + #[cfg(not(target_os = "macos"))] + event.window().hide().unwrap(); + } else { + win.close().unwrap(); + } + api.prevent_close(); + } + }) + .run(context) + .expect("error while running ChatGPT application"); } diff --git a/src-tauri/src/utils.rs b/src-tauri/src/utils.rs index a5625ec..9261e56 100644 --- a/src-tauri/src/utils.rs +++ b/src-tauri/src/utils.rs @@ -3,215 +3,180 @@ use log::info; use regex::Regex; use serde_json::Value; use std::{ - collections::HashMap, - fs::{self, File}, - path::{Path, PathBuf}, - process::Command, + collections::HashMap, + fs::{self, File}, + path::{Path, PathBuf}, + process::Command, }; use tauri::updater::UpdateResponse; use tauri::{utils::config::Config, AppHandle, Manager, Wry}; pub fn chat_root() -> PathBuf { - tauri::api::path::home_dir().unwrap().join(".chatgpt") + tauri::api::path::home_dir().unwrap().join(".chatgpt") } pub fn get_tauri_conf() -> Option { - 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) + 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() + Path::new(path).exists() } pub fn create_file(path: &Path) -> Result { - if let Some(p) = path.parent() { - fs::create_dir_all(p)? - } - File::create(path).map_err(Into::into) + if let Some(p) = path.parent() { + fs::create_dir_all(p)? + } + File::create(path).map_err(Into::into) } pub fn create_chatgpt_prompts() { - let sync_file = chat_root().join("cache_model").join("chatgpt_prompts.json"); - if !exists(&sync_file) { - create_file(&sync_file).unwrap(); - fs::write(&sync_file, "[]").unwrap(); - } + let sync_file = chat_root().join("cache_model").join("chatgpt_prompts.json"); + if !exists(&sync_file) { + create_file(&sync_file).unwrap(); + fs::write(&sync_file, "[]").unwrap(); + } } pub fn script_path() -> PathBuf { - 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(); - } + 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(); + } - script_file + script_file } pub fn user_script() -> String { - let user_script_content = fs::read_to_string(script_path()).unwrap_or_else(|_| "".to_string()); - format!( - "window.addEventListener('DOMContentLoaded', function() {{\n{}\n}})", - user_script_content - ) + let user_script_content = fs::read_to_string(script_path()).unwrap_or_else(|_| "".to_string()); + format!( + "window.addEventListener('DOMContentLoaded', function() {{\n{}\n}})", + user_script_content + ) } pub fn open_file(path: PathBuf) { - info!("open_file: {}", path.to_string_lossy()); - #[cfg(target_os = "macos")] - Command::new("open").arg("-R").arg(path).spawn().unwrap(); + info!("open_file: {}", path.to_string_lossy()); + #[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(); + #[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(); + // 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()); - } - }, - ); + 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()); + } + }); } pub fn merge(v: &Value, fields: &HashMap) -> Value { - match v { - Value::Object(m) => { - let mut m = m.clone(); - for (k, v) in fields { - m.insert(k.clone(), v.clone()); - } - Value::Object(m) - } - v => v.clone(), + match v { + Value::Object(m) => { + let mut m = m.clone(); + for (k, v) in fields { + m.insert(k.clone(), v.clone()); + } + Value::Object(m) } + v => v.clone(), + } } pub fn gen_cmd(name: String) -> String { - let re = Regex::new(r"[^a-zA-Z0-9]").unwrap(); - re.replace_all(&name, "_").to_lowercase() + let re = Regex::new(r"[^a-zA-Z0-9]").unwrap(); + re.replace_all(&name, "_").to_lowercase() } -pub async fn get_data( - url: &str, - app: Option<&tauri::AppHandle>, -) -> Result, reqwest::Error> { - let res = reqwest::get(url).await?; - let is_ok = res.status() == 200; - let body = res.text().await?; +pub async fn get_data(url: &str, app: Option<&tauri::AppHandle>) -> Result, reqwest::Error> { + let res = reqwest::get(url).await?; + let is_ok = res.status() == 200; + let body = res.text().await?; - if is_ok { - Ok(Some(body)) - } else { - info!("chatgpt_http_error: {}", body); - if let Some(v) = app { - tauri::api::dialog::message(v.get_window("core").as_ref(), "ChatGPT HTTP", body); - } - Ok(None) + if is_ok { + Ok(Some(body)) + } else { + info!("chatgpt_http_error: {}", body); + if let Some(v) = app { + tauri::api::dialog::message(v.get_window("core").as_ref(), "ChatGPT HTTP", body); } + Ok(None) + } } pub fn run_check_update(app: AppHandle, silent: bool, has_msg: Option) { - 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", - ); - } - } - }); + 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", + ); + } + } + }); } // Copy private api in tauri/updater/mod.rs. TODO: refactor to public api // Prompt a dialog asking if the user want to install the new version // Maybe we should add an option to customize it in future versions. pub async fn prompt_for_install(app: AppHandle, update: UpdateResponse) -> Result<()> { - info!("prompt_for_install"); - let windows = app.windows(); - let parent_window = windows.values().next(); - let package_info = app.package_info().clone(); + info!("prompt_for_install"); + let windows = app.windows(); + let parent_window = windows.values().next(); + let package_info = app.package_info().clone(); - let body = update.body().unwrap(); - // todo(lemarier): We should review this and make sure we have - // something more conventional. - let should_install = tauri::api::dialog::blocking::ask( - parent_window, - format!(r#"A new version of {} is available! "#, package_info.name), - format!( - r#"{} {} is now available -- you have {}. + let body = update.body().unwrap(); + // todo(lemarier): We should review this and make sure we have + // something more conventional. + let should_install = tauri::api::dialog::blocking::ask( + parent_window, + format!(r#"A new version of {} is available! "#, package_info.name), + format!( + r#"{} {} is now available -- you have {}. Would you like to install it now? Release Notes: {}"#, - package_info.name, - update.latest_version(), - package_info.version, - body - ), - ); - - if should_install { - // Launch updater download process - // macOS we display the `Ready to restart dialog` asking to restart - // Windows is closing the current App and launch the downloaded MSI when ready (the process stop here) - // Linux we replace the AppImage by launching a new install, it start a new AppImage instance, so we're closing the previous. (the process stop here) - update.download_and_install().await?; - - // Ask user if we need to restart the application - let should_exit = tauri::api::dialog::blocking::ask( - parent_window, - "Ready to Restart", - "The installation was successful, do you want to restart the application now?", - ); - if should_exit { - app.restart(); - } - } - - Ok(()) -} - -pub async fn silent_install(app: AppHandle, update: UpdateResponse) -> Result<()> { - info!("silent_install"); - let windows = app.windows(); - let parent_window = windows.values().next(); + package_info.name, + update.latest_version(), + package_info.version, + body + ), + ); + if should_install { // Launch updater download process // macOS we display the `Ready to restart dialog` asking to restart // Windows is closing the current App and launch the downloaded MSI when ready (the process stop here) @@ -220,33 +185,54 @@ pub async fn silent_install(app: AppHandle, update: UpdateResponse) -> // Ask user if we need to restart the application let should_exit = tauri::api::dialog::blocking::ask( - parent_window, - "Ready to Restart", - "The silent installation was successful, do you want to restart the application now?", + parent_window, + "Ready to Restart", + "The installation was successful, do you want to restart the application now?", ); if should_exit { - app.restart(); + app.restart(); } + } - Ok(()) + Ok(()) +} + +pub async fn silent_install(app: AppHandle, update: UpdateResponse) -> Result<()> { + info!("silent_install"); + let windows = app.windows(); + let parent_window = windows.values().next(); + + // Launch updater download process + // macOS we display the `Ready to restart dialog` asking to restart + // Windows is closing the current App and launch the downloaded MSI when ready (the process stop here) + // Linux we replace the AppImage by launching a new install, it start a new AppImage instance, so we're closing the previous. (the process stop here) + update.download_and_install().await?; + + // Ask user if we need to restart the application + let should_exit = tauri::api::dialog::blocking::ask( + parent_window, + "Ready to Restart", + "The silent installation was successful, do you want to restart the application now?", + ); + if should_exit { + app.restart(); + } + + Ok(()) } pub fn is_hidden(entry: &walkdir::DirEntry) -> bool { - entry - .file_name() - .to_str() - .map(|s| s.starts_with('.')) - .unwrap_or(false) + entry.file_name().to_str().map(|s| s.starts_with('.')).unwrap_or(false) } pub fn vec_to_hashmap( - vec: impl Iterator, - key: &str, - map: &mut HashMap, + vec: impl Iterator, + key: &str, + map: &mut HashMap, ) { - for v in vec { - if let Some(kval) = v.get(key).and_then(serde_json::Value::as_str) { - map.insert(kval.to_string(), v); - } + for v in vec { + if let Some(kval) = v.get(key).and_then(serde_json::Value::as_str) { + map.insert(kval.to_string(), v); } + } } diff --git a/src/view/awesome/config.tsx b/src/view/awesome/config.tsx index 208c752..4e27247 100644 --- a/src/view/awesome/config.tsx +++ b/src/view/awesome/config.tsx @@ -12,7 +12,7 @@ export const awesomeColumns = () => [ title: 'URL', dataIndex: 'url', key: 'url', - width: 120, + width: 200, }, // { // title: 'Icon', @@ -33,7 +33,7 @@ export const awesomeColumns = () => [ title: 'Category', dataIndex: 'category', key: 'category', - width: 200, + width: 120, render: (v: string) => {v} }, {