use anyhow::Result; use log::{error, info}; use regex::Regex; use serde_json::Value; use std::{ collections::HashMap, fs::{self, File}, path::{Path, PathBuf}, process::Command, }; use tauri::updater::UpdateResponse; use tauri::{utils::config::Config, AppHandle, Manager, Wry}; pub fn app_root() -> PathBuf { 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) } pub fn exists(path: &Path) -> bool { 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) } pub fn create_chatgpt_prompts() { 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(); } } pub fn script_path() -> PathBuf { let script_file = app_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 } 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 ) } pub fn open_file(path: PathBuf) { let pathname = convert_path(path.to_str().unwrap()); info!("open_file: {}", pathname); #[cfg(target_os = "macos")] Command::new("open") .arg("-R") .arg(pathname) .spawn() .unwrap(); #[cfg(target_os = "windows")] Command::new("explorer.exe") .arg("/select,") .arg(pathname) .spawn() .unwrap(); // https://askubuntu.com/a/31071 #[cfg(target_os = "linux")] Command::new("xdg-open").arg(pathname).spawn().unwrap(); } pub fn convert_path(path_str: &str) -> String { if cfg!(target_os = "windows") { path_str.replace('/', "\\") } else { String::from(path_str) } } pub fn clear_conf(app: &tauri::AppHandle) { 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 Note: The application will exit automatically after the configuration cleanup!", 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(); std::process::exit(0); } }, ); } 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(), } } pub fn gen_cmd(name: String) -> String { 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?; if is_ok { Ok(Some(body)) } else { error!("chatgpt_http: {}", 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 { 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", ); } } } }); } // 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(); 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(); // 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) } pub fn vec_to_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); } } }