mirror of
https://github.com/lencx/ChatGPT.git
synced 2024-10-01 01:06:13 -04:00
Merge pull request #206 from lencx/dev
This commit is contained in:
commit
26bd845a72
@ -201,8 +201,9 @@ Mac 上无法安装,提示开发者未验证,具体可以查看下面给出
|
|||||||
|
|
||||||
#### 预安装
|
#### 预安装
|
||||||
|
|
||||||
- [Rust](https://www.rust-lang.org/)
|
- [Rust (必须)](https://www.rust-lang.org/)
|
||||||
- [VS Code](https://code.visualstudio.com/)
|
- [Node.js (必须)](https://nodejs.org/)
|
||||||
|
- [VS Code (可选)](https://code.visualstudio.com/)
|
||||||
- [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer)
|
- [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer)
|
||||||
- [tauri](https://marketplace.visualstudio.com/items?itemName=tauri-apps.tauri-vscode)
|
- [tauri](https://marketplace.visualstudio.com/items?itemName=tauri-apps.tauri-vscode)
|
||||||
|
|
||||||
@ -226,6 +227,9 @@ yarn dev
|
|||||||
yarn build
|
yarn build
|
||||||
```
|
```
|
||||||
|
|
||||||
|
- [The distDir configuration is set to "../dist" but this path doesn't exist](https://github.com/lencx/ChatGPT/discussions/180)
|
||||||
|
- [Error A public key has been found, but no private key. Make sure to set TAURI_PRIVATE_KEY environment variable.](https://github.com/lencx/ChatGPT/discussions/182)
|
||||||
|
|
||||||
## ❤️ 感谢
|
## ❤️ 感谢
|
||||||
|
|
||||||
- 分享按钮的代码从 [@liady](https://github.com/liady) 的插件获得,并做了一些本地化修改
|
- 分享按钮的代码从 [@liady](https://github.com/liady) 的插件获得,并做了一些本地化修改
|
||||||
|
10
README.md
10
README.md
@ -88,7 +88,7 @@ You can look at **[awesome-chatgpt-prompts](https://github.com/f/awesome-chatgpt
|
|||||||
## ✨ Features
|
## ✨ Features
|
||||||
|
|
||||||
- Multi-platform: `macOS` `Linux` `Windows`
|
- Multi-platform: `macOS` `Linux` `Windows`
|
||||||
- Export ChatGPT history (PNG, PDF and Share Link)
|
- Export ChatGPT history (PNG, PDF and Markdown)
|
||||||
- Automatic application upgrade notification
|
- Automatic application upgrade notification
|
||||||
- Common shortcut keys
|
- Common shortcut keys
|
||||||
- System tray hover window
|
- System tray hover window
|
||||||
@ -209,8 +209,9 @@ It's safe, just a wrapper for [OpenAI ChatGPT](https://chat.openai.com) website,
|
|||||||
|
|
||||||
#### PreInstall
|
#### PreInstall
|
||||||
|
|
||||||
- [Rust](https://www.rust-lang.org/)
|
- [Rust (Required)](https://www.rust-lang.org/)
|
||||||
- [VS Code](https://code.visualstudio.com/)
|
- [Node.js (Required)](https://nodejs.org/)
|
||||||
|
- [VS Code (Optional)](https://code.visualstudio.com/)
|
||||||
- [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer)
|
- [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer)
|
||||||
- [tauri](https://marketplace.visualstudio.com/items?itemName=tauri-apps.tauri-vscode)
|
- [tauri](https://marketplace.visualstudio.com/items?itemName=tauri-apps.tauri-vscode)
|
||||||
|
|
||||||
@ -234,6 +235,9 @@ yarn dev
|
|||||||
yarn build
|
yarn build
|
||||||
```
|
```
|
||||||
|
|
||||||
|
- [The distDir configuration is set to "../dist" but this path doesn't exist](https://github.com/lencx/ChatGPT/discussions/180)
|
||||||
|
- [Error A public key has been found, but no private key. Make sure to set TAURI_PRIVATE_KEY environment variable.](https://github.com/lencx/ChatGPT/discussions/182)
|
||||||
|
|
||||||
## ❤️ Thanks
|
## ❤️ Thanks
|
||||||
|
|
||||||
- The core implementation of the share button code was copied from the [@liady](https://github.com/liady) extension with some modifications.
|
- The core implementation of the share button code was copied from the [@liady](https://github.com/liady) extension with some modifications.
|
||||||
|
@ -1,5 +1,14 @@
|
|||||||
# UPDATE LOG
|
# UPDATE LOG
|
||||||
|
|
||||||
|
## v0.9.0
|
||||||
|
|
||||||
|
fix:
|
||||||
|
- export button does not work
|
||||||
|
|
||||||
|
feat:
|
||||||
|
- add an export markdown button
|
||||||
|
- `Control Center` adds `Notes` and `Download` menus for managing exported chat files (Markdown, PNG, PDF). `Notes` supports markdown previews.
|
||||||
|
|
||||||
## v0.8.1
|
## v0.8.1
|
||||||
|
|
||||||
fix:
|
fix:
|
||||||
|
@ -40,7 +40,9 @@
|
|||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
|
"react-markdown": "^8.0.4",
|
||||||
"react-router-dom": "^6.4.5",
|
"react-router-dom": "^6.4.5",
|
||||||
|
"react-syntax-highlighter": "^15.5.0",
|
||||||
"uuid": "^9.0.0"
|
"uuid": "^9.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -50,6 +52,7 @@
|
|||||||
"@types/node": "^18.7.10",
|
"@types/node": "^18.7.10",
|
||||||
"@types/react": "^18.0.15",
|
"@types/react": "^18.0.15",
|
||||||
"@types/react-dom": "^18.0.6",
|
"@types/react-dom": "^18.0.6",
|
||||||
|
"@types/react-syntax-highlighter": "^15.5.6",
|
||||||
"@types/uuid": "^9.0.0",
|
"@types/uuid": "^9.0.0",
|
||||||
"@vitejs/plugin-react": "^3.0.0",
|
"@vitejs/plugin-react": "^3.0.0",
|
||||||
"sass": "^1.56.2",
|
"sass": "^1.56.2",
|
||||||
|
@ -16,18 +16,18 @@ tauri-build = {version = "1.2.1", features = [] }
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.66"
|
anyhow = "1.0.66"
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
|
||||||
tauri = { version = "1.2.3", features = ["api-all", "devtools", "global-shortcut", "system-tray", "updater"] }
|
|
||||||
tauri-plugin-positioner = { version = "1.0.4", features = ["system-tray"] }
|
|
||||||
log = "0.4.17"
|
log = "0.4.17"
|
||||||
csv = "1.1.6"
|
csv = "1.1.6"
|
||||||
thiserror = "1.0.38"
|
thiserror = "1.0.38"
|
||||||
walkdir = "2.3.2"
|
walkdir = "2.3.2"
|
||||||
regex = "1.7.0"
|
regex = "1.7.0"
|
||||||
tokio = { version = "1.23.0", features = ["macros"] }
|
|
||||||
reqwest = "0.11.13"
|
reqwest = "0.11.13"
|
||||||
wry = "0.23.4"
|
wry = "0.24.1"
|
||||||
dark-light = "1.0.0"
|
dark-light = "1.0.0"
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
tokio = { version = "1.23.0", features = ["macros"] }
|
||||||
|
tauri-plugin-positioner = { version = "1.0.4", features = ["system-tray"] }
|
||||||
|
tauri = { version = "1.2.3", features = ["api-all", "devtools", "global-shortcut", "system-tray", "updater"] }
|
||||||
[dependencies.tauri-plugin-log]
|
[dependencies.tauri-plugin-log]
|
||||||
git = "https://github.com/lencx/tauri-plugin-log"
|
git = "https://github.com/lencx/tauri-plugin-log"
|
||||||
branch = "dev"
|
branch = "dev"
|
||||||
@ -36,6 +36,8 @@ features = ["colored"]
|
|||||||
git = "https://github.com/lencx/tauri-plugin-autostart"
|
git = "https://github.com/lencx/tauri-plugin-autostart"
|
||||||
branch = "dev"
|
branch = "dev"
|
||||||
|
|
||||||
|
# sqlx = { version = "0.6.2", features = ["runtime-tokio-rustls", "sqlite"] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
# by default Tauri runs in production mode
|
# by default Tauri runs in production mode
|
||||||
# when `tauri dev` runs it is executed with `cargo run --no-default-features` if `devPath` is an URL
|
# when `tauri dev` runs it is executed with `cargo run --no-default-features` if `devPath` is an URL
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
app::window,
|
app::{fs_extra, window},
|
||||||
conf::{ChatConfJson, GITHUB_PROMPTS_CSV_URL},
|
conf::{ChatConfJson, GITHUB_PROMPTS_CSV_URL},
|
||||||
utils,
|
utils::{self, chat_root, create_file},
|
||||||
};
|
};
|
||||||
use log::info;
|
use log::info;
|
||||||
use std::{collections::HashMap, fs, path::PathBuf};
|
use regex::Regex;
|
||||||
|
use std::{collections::HashMap, fs, path::PathBuf, vec};
|
||||||
use tauri::{api, command, AppHandle, Manager, Theme};
|
use tauri::{api, command, AppHandle, Manager, Theme};
|
||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
@ -35,11 +36,20 @@ pub fn fullscreen(app: AppHandle) {
|
|||||||
|
|
||||||
#[command]
|
#[command]
|
||||||
pub fn download(_app: AppHandle, name: String, blob: Vec<u8>) {
|
pub fn download(_app: AppHandle, name: String, blob: Vec<u8>) {
|
||||||
let path = api::path::download_dir().unwrap().join(name);
|
let path = chat_root().join(PathBuf::from(name));
|
||||||
|
create_file(&path).unwrap();
|
||||||
fs::write(&path, blob).unwrap();
|
fs::write(&path, blob).unwrap();
|
||||||
utils::open_file(path);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
#[command]
|
#[command]
|
||||||
pub fn open_link(app: AppHandle, url: String) {
|
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();
|
||||||
@ -167,6 +177,93 @@ pub fn cmd_list() -> Vec<ModelRecord> {
|
|||||||
list
|
list
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
|
||||||
|
pub struct FileMetadata {
|
||||||
|
pub name: String,
|
||||||
|
pub ext: String,
|
||||||
|
pub created: u64,
|
||||||
|
pub id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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 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::<Vec<serde_json::Value>>(&content).unwrap_or_else(|err| {
|
||||||
|
info!("download_list_parse_error: {}", err);
|
||||||
|
vec![]
|
||||||
|
});
|
||||||
|
|
||||||
|
(list, download_path)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[command]
|
||||||
|
pub fn download_list(pathname: &str, dir: &str, filename: Option<String>, id: Option<String>) {
|
||||||
|
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<id>[\d\w]+).(?P<ext>\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()),
|
||||||
|
};
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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()
|
||||||
|
});
|
||||||
|
|
||||||
|
fs::write(data.1, serde_json::to_string_pretty(&list).unwrap()).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
#[command]
|
#[command]
|
||||||
pub async fn sync_prompts(app: AppHandle, time: u64) -> Option<Vec<ModelRecord>> {
|
pub async fn sync_prompts(app: AppHandle, time: u64) -> Option<Vec<ModelRecord>> {
|
||||||
let res = utils::get_data(GITHUB_PROMPTS_CSV_URL, Some(&app))
|
let res = utils::get_data(GITHUB_PROMPTS_CSV_URL, Some(&app))
|
||||||
|
@ -60,7 +60,7 @@ struct UnixMetadata {
|
|||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Metadata {
|
pub struct Metadata {
|
||||||
accessed_at_ms: u64,
|
accessed_at_ms: u64,
|
||||||
created_at_ms: u64,
|
pub created_at_ms: u64,
|
||||||
modified_at_ms: u64,
|
modified_at_ms: u64,
|
||||||
is_dir: bool,
|
is_dir: bool,
|
||||||
is_file: bool,
|
is_file: bool,
|
||||||
@ -74,7 +74,7 @@ pub struct Metadata {
|
|||||||
file_attributes: u32,
|
file_attributes: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn system_time_to_ms(time: std::io::Result<SystemTime>) -> u64 {
|
pub fn system_time_to_ms(time: std::io::Result<SystemTime>) -> u64 {
|
||||||
time.map(|t| {
|
time.map(|t| {
|
||||||
let duration_since_epoch = t.duration_since(UNIX_EPOCH).unwrap();
|
let duration_since_epoch = t.duration_since(UNIX_EPOCH).unwrap();
|
||||||
duration_since_epoch.as_millis() as u64
|
duration_since_epoch.as_millis() as u64
|
||||||
|
@ -41,10 +41,6 @@ pub fn init() -> Menu {
|
|||||||
stay_on_top
|
stay_on_top
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
let titlebar =
|
|
||||||
CustomMenuItem::new("titlebar".to_string(), "Titlebar").accelerator("CmdOrCtrl+B");
|
|
||||||
|
|
||||||
let theme_light = CustomMenuItem::new("theme_light".to_string(), "Light");
|
let theme_light = CustomMenuItem::new("theme_light".to_string(), "Light");
|
||||||
let theme_dark = CustomMenuItem::new("theme_dark".to_string(), "Dark");
|
let theme_dark = CustomMenuItem::new("theme_dark".to_string(), "Dark");
|
||||||
let theme_system = CustomMenuItem::new("theme_system".to_string(), "System");
|
let theme_system = CustomMenuItem::new("theme_system".to_string(), "System");
|
||||||
@ -62,6 +58,9 @@ pub fn init() -> Menu {
|
|||||||
popup_search
|
popup_search
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
let titlebar =
|
||||||
|
CustomMenuItem::new("titlebar".to_string(), "Titlebar").accelerator("CmdOrCtrl+B");
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
let titlebar_menu = if chat_conf.titlebar {
|
let titlebar_menu = if chat_conf.titlebar {
|
||||||
titlebar.selected()
|
titlebar.selected()
|
||||||
@ -69,6 +68,13 @@ pub fn init() -> Menu {
|
|||||||
titlebar
|
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 preferences_menu = Submenu::new(
|
let preferences_menu = Submenu::new(
|
||||||
"Preferences",
|
"Preferences",
|
||||||
Menu::with_items([
|
Menu::with_items([
|
||||||
@ -81,6 +87,7 @@ pub fn init() -> Menu {
|
|||||||
titlebar_menu.into(),
|
titlebar_menu.into(),
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
CustomMenuItem::new("hide_dock_icon".to_string(), "Hide Dock Icon").into(),
|
CustomMenuItem::new("hide_dock_icon".to_string(), "Hide Dock Icon").into(),
|
||||||
|
system_tray_menu.into(),
|
||||||
CustomMenuItem::new("inject_script".to_string(), "Inject Script")
|
CustomMenuItem::new("inject_script".to_string(), "Inject Script")
|
||||||
.accelerator("CmdOrCtrl+J")
|
.accelerator("CmdOrCtrl+J")
|
||||||
.into(),
|
.into(),
|
||||||
@ -141,6 +148,7 @@ pub fn init() -> Menu {
|
|||||||
CustomMenuItem::new("awesome".to_string(), "Awesome ChatGPT")
|
CustomMenuItem::new("awesome".to_string(), "Awesome ChatGPT")
|
||||||
.accelerator("CmdOrCtrl+Shift+A")
|
.accelerator("CmdOrCtrl+Shift+A")
|
||||||
.into(),
|
.into(),
|
||||||
|
CustomMenuItem::new("buy_coffee".to_string(), "Buy lencx a coffee").into(),
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -242,6 +250,7 @@ pub fn menu_handler(event: WindowMenuEvent<tauri::Wry>) {
|
|||||||
"go_conf" => utils::open_file(utils::chat_root()),
|
"go_conf" => utils::open_file(utils::chat_root()),
|
||||||
"clear_conf" => utils::clear_conf(&app),
|
"clear_conf" => utils::clear_conf(&app),
|
||||||
"awesome" => open(&app, conf::AWESOME_URL.to_string()),
|
"awesome" => open(&app, conf::AWESOME_URL.to_string()),
|
||||||
|
"buy_coffee" => open(&app, conf::BUY_COFFEE.to_string()),
|
||||||
"popup_search" => {
|
"popup_search" => {
|
||||||
let chat_conf = conf::ChatConfJson::get_chat_conf();
|
let chat_conf = conf::ChatConfJson::get_chat_conf();
|
||||||
let popup_search = !chat_conf.popup_search;
|
let popup_search = !chat_conf.popup_search;
|
||||||
@ -281,6 +290,11 @@ pub fn menu_handler(event: WindowMenuEvent<tauri::Wry>) {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
tauri::api::process::restart(&app.env());
|
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" => {
|
"theme_light" | "theme_dark" | "theme_system" => {
|
||||||
let theme = match menu_id {
|
let theme = match menu_id {
|
||||||
"theme_dark" => "Dark",
|
"theme_dark" => "Dark",
|
||||||
|
@ -61,14 +61,18 @@ pub fn init(app: &mut App) -> std::result::Result<(), Box<dyn std::error::Error>
|
|||||||
.always_on_top(chat_conf.stay_on_top)
|
.always_on_top(chat_conf.stay_on_top)
|
||||||
.title_bar_style(ChatConfJson::titlebar())
|
.title_bar_style(ChatConfJson::titlebar())
|
||||||
.initialization_script(&utils::user_script())
|
.initialization_script(&utils::user_script())
|
||||||
|
.initialization_script(include_str!("../vendors/jq.js"))
|
||||||
.initialization_script(include_str!("../vendors/floating-ui-core.js"))
|
.initialization_script(include_str!("../vendors/floating-ui-core.js"))
|
||||||
.initialization_script(include_str!("../vendors/floating-ui-dom.js"))
|
.initialization_script(include_str!("../vendors/floating-ui-dom.js"))
|
||||||
.initialization_script(include_str!("../vendors/html2canvas.js"))
|
.initialization_script(include_str!("../vendors/html2canvas.js"))
|
||||||
.initialization_script(include_str!("../vendors/jspdf.js"))
|
.initialization_script(include_str!("../vendors/jspdf.js"))
|
||||||
.initialization_script(include_str!("../assets/core.js"))
|
.initialization_script(include_str!("../vendors/turndown.js"))
|
||||||
.initialization_script(include_str!("../assets/popup.core.js"))
|
.initialization_script(include_str!("../vendors/turndown-plugin-gfm.js"))
|
||||||
.initialization_script(include_str!("../assets/export.js"))
|
.initialization_script(include_str!("../scripts/core.js"))
|
||||||
.initialization_script(include_str!("../assets/cmd.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)
|
.user_agent(&chat_conf.ua_window)
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@ -82,14 +86,18 @@ pub fn init(app: &mut App) -> std::result::Result<(), Box<dyn std::error::Error>
|
|||||||
.theme(theme)
|
.theme(theme)
|
||||||
.always_on_top(chat_conf.stay_on_top)
|
.always_on_top(chat_conf.stay_on_top)
|
||||||
.initialization_script(&utils::user_script())
|
.initialization_script(&utils::user_script())
|
||||||
|
.initialization_script(include_str!("../vendors/jq.js"))
|
||||||
.initialization_script(include_str!("../vendors/floating-ui-core.js"))
|
.initialization_script(include_str!("../vendors/floating-ui-core.js"))
|
||||||
.initialization_script(include_str!("../vendors/floating-ui-dom.js"))
|
.initialization_script(include_str!("../vendors/floating-ui-dom.js"))
|
||||||
.initialization_script(include_str!("../vendors/html2canvas.js"))
|
.initialization_script(include_str!("../vendors/html2canvas.js"))
|
||||||
.initialization_script(include_str!("../vendors/jspdf.js"))
|
.initialization_script(include_str!("../vendors/jspdf.js"))
|
||||||
.initialization_script(include_str!("../assets/core.js"))
|
.initialization_script(include_str!("../vendors/turndown.js"))
|
||||||
.initialization_script(include_str!("../assets/popup.core.js"))
|
.initialization_script(include_str!("../vendors/turndown-plugin-gfm.js"))
|
||||||
.initialization_script(include_str!("../assets/export.js"))
|
.initialization_script(include_str!("../scripts/core.js"))
|
||||||
.initialization_script(include_str!("../assets/cmd.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)
|
.user_agent(&chat_conf.ua_window)
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -18,11 +18,12 @@ pub fn tray_window(handle: &tauri::AppHandle) {
|
|||||||
.always_on_top(true)
|
.always_on_top(true)
|
||||||
.theme(theme)
|
.theme(theme)
|
||||||
.initialization_script(&utils::user_script())
|
.initialization_script(&utils::user_script())
|
||||||
|
.initialization_script(include_str!("../vendors/jq.js"))
|
||||||
.initialization_script(include_str!("../vendors/floating-ui-core.js"))
|
.initialization_script(include_str!("../vendors/floating-ui-core.js"))
|
||||||
.initialization_script(include_str!("../vendors/floating-ui-dom.js"))
|
.initialization_script(include_str!("../vendors/floating-ui-dom.js"))
|
||||||
.initialization_script(include_str!("../assets/core.js"))
|
.initialization_script(include_str!("../scripts/core.js"))
|
||||||
.initialization_script(include_str!("../assets/cmd.js"))
|
.initialization_script(include_str!("../scripts/cmd.js"))
|
||||||
.initialization_script(include_str!("../assets/popup.core.js"))
|
.initialization_script(include_str!("../scripts/popup.core.js"))
|
||||||
.user_agent(&chat_conf.ua_tray)
|
.user_agent(&chat_conf.ua_tray)
|
||||||
.build()
|
.build()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
@ -73,9 +74,10 @@ pub fn dalle2_window(
|
|||||||
.inner_size(800.0, 600.0)
|
.inner_size(800.0, 600.0)
|
||||||
.always_on_top(false)
|
.always_on_top(false)
|
||||||
.theme(theme)
|
.theme(theme)
|
||||||
.initialization_script(include_str!("../assets/core.js"))
|
.initialization_script(include_str!("../vendors/jq.js"))
|
||||||
|
.initialization_script(include_str!("../scripts/core.js"))
|
||||||
.initialization_script(&query)
|
.initialization_script(&query)
|
||||||
.initialization_script(include_str!("../assets/dalle2.js"))
|
.initialization_script(include_str!("../scripts/dalle2.js"))
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
});
|
});
|
||||||
|
@ -14,12 +14,14 @@ use tauri::TitleBarStyle;
|
|||||||
pub const ISSUES_URL: &str = "https://github.com/lencx/ChatGPT/issues";
|
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 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 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 =
|
pub const GITHUB_PROMPTS_CSV_URL: &str =
|
||||||
"https://raw.githubusercontent.com/f/awesome-chatgpt-prompts/main/prompts.csv";
|
"https://raw.githubusercontent.com/f/awesome-chatgpt-prompts/main/prompts.csv";
|
||||||
pub const DEFAULT_CHAT_CONF: &str = r#"{
|
pub const DEFAULT_CHAT_CONF: &str = r#"{
|
||||||
"stay_on_top": false,
|
"stay_on_top": false,
|
||||||
"auto_update": "Prompt",
|
"auto_update": "Prompt",
|
||||||
"theme": "Light",
|
"theme": "Light",
|
||||||
|
"tray": true,
|
||||||
"titlebar": true,
|
"titlebar": true,
|
||||||
"popup_search": true,
|
"popup_search": true,
|
||||||
"global_shortcut": "",
|
"global_shortcut": "",
|
||||||
@ -33,6 +35,7 @@ pub const DEFAULT_CHAT_CONF_MAC: &str = r#"{
|
|||||||
"stay_on_top": false,
|
"stay_on_top": false,
|
||||||
"auto_update": "Prompt",
|
"auto_update": "Prompt",
|
||||||
"theme": "Light",
|
"theme": "Light",
|
||||||
|
"tray": true,
|
||||||
"titlebar": false,
|
"titlebar": false,
|
||||||
"popup_search": true,
|
"popup_search": true,
|
||||||
"global_shortcut": "",
|
"global_shortcut": "",
|
||||||
@ -53,6 +56,7 @@ pub struct ChatConfJson {
|
|||||||
pub theme: String,
|
pub theme: String,
|
||||||
// auto update policy, Prompt/Silent/Disable
|
// auto update policy, Prompt/Silent/Disable
|
||||||
pub auto_update: String,
|
pub auto_update: String,
|
||||||
|
pub tray: bool,
|
||||||
pub popup_search: bool,
|
pub popup_search: bool,
|
||||||
pub stay_on_top: bool,
|
pub stay_on_top: bool,
|
||||||
pub default_origin: String,
|
pub default_origin: String,
|
||||||
|
@ -30,7 +30,12 @@ async fn main() {
|
|||||||
trace: Color::Cyan,
|
trace: Color::Cyan,
|
||||||
};
|
};
|
||||||
|
|
||||||
tauri::Builder::default()
|
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 mut builder = tauri::Builder::default()
|
||||||
// https://github.com/tauri-apps/tauri/pull/2736
|
// https://github.com/tauri-apps/tauri/pull/2736
|
||||||
.plugin(
|
.plugin(
|
||||||
LoggerBuilder::new()
|
LoggerBuilder::new()
|
||||||
@ -45,10 +50,16 @@ async fn main() {
|
|||||||
])
|
])
|
||||||
.build(),
|
.build(),
|
||||||
)
|
)
|
||||||
|
.plugin(tauri_plugin_positioner::init())
|
||||||
|
.plugin(tauri_plugin_autostart::init(
|
||||||
|
MacosLauncher::LaunchAgent,
|
||||||
|
None,
|
||||||
|
))
|
||||||
.invoke_handler(tauri::generate_handler![
|
.invoke_handler(tauri::generate_handler![
|
||||||
cmd::drag_window,
|
cmd::drag_window,
|
||||||
cmd::fullscreen,
|
cmd::fullscreen,
|
||||||
cmd::download,
|
cmd::download,
|
||||||
|
cmd::save_file,
|
||||||
cmd::open_link,
|
cmd::open_link,
|
||||||
cmd::get_chat_conf,
|
cmd::get_chat_conf,
|
||||||
cmd::get_theme,
|
cmd::get_theme,
|
||||||
@ -65,16 +76,18 @@ async fn main() {
|
|||||||
cmd::window_reload,
|
cmd::window_reload,
|
||||||
cmd::dalle2_window,
|
cmd::dalle2_window,
|
||||||
cmd::cmd_list,
|
cmd::cmd_list,
|
||||||
|
cmd::download_list,
|
||||||
|
cmd::get_download_list,
|
||||||
fs_extra::metadata,
|
fs_extra::metadata,
|
||||||
])
|
])
|
||||||
.setup(setup::init)
|
.setup(setup::init)
|
||||||
.plugin(tauri_plugin_positioner::init())
|
.menu(menu::init());
|
||||||
.plugin(tauri_plugin_autostart::init(
|
|
||||||
MacosLauncher::LaunchAgent,
|
if chat_conf.tray {
|
||||||
None,
|
builder = builder.system_tray(menu::tray_menu());
|
||||||
))
|
}
|
||||||
.menu(menu::init())
|
|
||||||
.system_tray(menu::tray_menu())
|
builder
|
||||||
.on_menu_event(menu::menu_handler)
|
.on_menu_event(menu::menu_handler)
|
||||||
.on_system_tray_event(menu::tray_handler)
|
.on_system_tray_event(menu::tray_handler)
|
||||||
.on_window_event(|event| {
|
.on_window_event(|event| {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// *** Core Script - CMD ***
|
// *** Core Script - CMD ***
|
||||||
|
|
||||||
function init() {
|
$(function() {
|
||||||
const styleDom = document.createElement('style');
|
const styleDom = document.createElement('style');
|
||||||
styleDom.innerHTML = `form {
|
styleDom.innerHTML = `form {
|
||||||
position: relative;
|
position: relative;
|
||||||
@ -71,9 +71,9 @@ function init() {
|
|||||||
width: 20px;
|
width: 20px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
}
|
}
|
||||||
.chatappico.pdf {
|
.chatappico.pdf, .chatappico.md {
|
||||||
width: 24px;
|
width: 22px;
|
||||||
height: 24px;
|
height: 22px;
|
||||||
}
|
}
|
||||||
@media screen and (max-width: 767px) {
|
@media screen and (max-width: 767px) {
|
||||||
#download-png-button, #download-pdf-button, #download-html-button {
|
#download-png-button, #download-pdf-button, #download-html-button {
|
||||||
@ -92,7 +92,7 @@ function init() {
|
|||||||
clearInterval(window.formInterval);
|
clearInterval(window.formInterval);
|
||||||
cmdTip();
|
cmdTip();
|
||||||
}, 200);
|
}, 200);
|
||||||
}
|
});
|
||||||
|
|
||||||
async function cmdTip() {
|
async function cmdTip() {
|
||||||
const chatModelJson = await invoke('get_chat_model_cmd') || {};
|
const chatModelJson = await invoke('get_chat_model_cmd') || {};
|
||||||
@ -269,12 +269,3 @@ async function cmdTip() {
|
|||||||
});
|
});
|
||||||
}, 200);
|
}, 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
|
||||||
document.readyState === "complete" ||
|
|
||||||
document.readyState === "interactive"
|
|
||||||
) {
|
|
||||||
init();
|
|
||||||
} else {
|
|
||||||
document.addEventListener("DOMContentLoaded", init);
|
|
||||||
}
|
|
@ -40,7 +40,7 @@ window.uid = uid;
|
|||||||
window.invoke = invoke;
|
window.invoke = invoke;
|
||||||
window.transformCallback = transformCallback;
|
window.transformCallback = transformCallback;
|
||||||
|
|
||||||
async function init() {
|
$(async function () {
|
||||||
if (__TAURI_METADATA__.__currentWindow.label === 'tray') {
|
if (__TAURI_METADATA__.__currentWindow.label === 'tray') {
|
||||||
document.getElementsByTagName('html')[0].style['font-size'] = '70%';
|
document.getElementsByTagName('html')[0].style['font-size'] = '70%';
|
||||||
}
|
}
|
||||||
@ -91,13 +91,4 @@ async function init() {
|
|||||||
window.__sync_prompts = async function() {
|
window.__sync_prompts = async function() {
|
||||||
await invoke('sync_prompts', { time: Date.now() });
|
await invoke('sync_prompts', { time: Date.now() });
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
if (
|
|
||||||
document.readyState === "complete" ||
|
|
||||||
document.readyState === "interactive"
|
|
||||||
) {
|
|
||||||
init();
|
|
||||||
} else {
|
|
||||||
document.addEventListener("DOMContentLoaded", init);
|
|
||||||
}
|
|
@ -1,6 +1,6 @@
|
|||||||
// *** Core Script - DALL·E 2 ***
|
// *** Core Script - DALL·E 2 ***
|
||||||
|
|
||||||
async function init() {
|
$(function () {
|
||||||
document.addEventListener("click", (e) => {
|
document.addEventListener("click", (e) => {
|
||||||
const origin = e.target.closest("a");
|
const origin = e.target.closest("a");
|
||||||
if (!origin || !origin.target) return;
|
if (!origin || !origin.target) return;
|
||||||
@ -28,13 +28,4 @@ async function init() {
|
|||||||
searchInput.value = query;
|
searchInput.value = query;
|
||||||
}
|
}
|
||||||
}, 200)
|
}, 200)
|
||||||
}
|
})
|
||||||
|
|
||||||
if (
|
|
||||||
document.readyState === "complete" ||
|
|
||||||
document.readyState === "interactive"
|
|
||||||
) {
|
|
||||||
init();
|
|
||||||
} else {
|
|
||||||
document.addEventListener("DOMContentLoaded", init);
|
|
||||||
}
|
|
@ -1,8 +1,8 @@
|
|||||||
// *** Core Script - Export ***
|
// *** Core Script - Export ***
|
||||||
// @ref: https://github.com/liady/ChatGPT-pdf
|
|
||||||
|
|
||||||
const buttonOuterHTMLFallback = `<button class="btn flex justify-center gap-2 btn-neutral" id="download-png-button">Try Again</button>`;
|
const buttonOuterHTMLFallback = `<button class="btn flex justify-center gap-2 btn-neutral" id="download-png-button">Try Again</button>`;
|
||||||
async function init() {
|
|
||||||
|
$(async function () {
|
||||||
if (window.innerWidth < 767) return;
|
if (window.innerWidth < 767) return;
|
||||||
const chatConf = await invoke('get_chat_conf') || {};
|
const chatConf = await invoke('get_chat_conf') || {};
|
||||||
if (window.buttonsInterval) {
|
if (window.buttonsInterval) {
|
||||||
@ -25,7 +25,7 @@ async function init() {
|
|||||||
removeButtons();
|
removeButtons();
|
||||||
}
|
}
|
||||||
}, 1000);
|
}, 1000);
|
||||||
}
|
})
|
||||||
|
|
||||||
const Format = {
|
const Format = {
|
||||||
PNG: "png",
|
PNG: "png",
|
||||||
@ -49,14 +49,19 @@ function shouldAddButtons(actionsArea) {
|
|||||||
const buttons = actionsArea.querySelectorAll("button");
|
const buttons = actionsArea.querySelectorAll("button");
|
||||||
|
|
||||||
const hasTryAgainButton = Array.from(buttons).some((button) => {
|
const hasTryAgainButton = Array.from(buttons).some((button) => {
|
||||||
return !button.id?.includes("download");
|
return !/download-/.test(button.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
// fix: https://github.com/lencx/ChatGPT/issues/189
|
const stopBtn = buttons?.[0]?.innerText;
|
||||||
if (buttons.length === 1) {
|
|
||||||
|
if (/Stop generating/ig.test(stopBtn)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (buttons.length === 2 && (/Regenerate response/ig.test(stopBtn) || buttons[1].innerText === '')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (hasTryAgainButton && buttons.length === 1) {
|
if (hasTryAgainButton && buttons.length === 1) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -80,51 +85,58 @@ function shouldAddButtons(actionsArea) {
|
|||||||
function removeButtons() {
|
function removeButtons() {
|
||||||
const downloadButton = document.getElementById("download-png-button");
|
const downloadButton = document.getElementById("download-png-button");
|
||||||
const downloadPdfButton = document.getElementById("download-pdf-button");
|
const downloadPdfButton = document.getElementById("download-pdf-button");
|
||||||
const downloadHtmlButton = document.getElementById("download-html-button");
|
const downloadMdButton = document.getElementById("download-markdown-button");
|
||||||
if (downloadButton) {
|
if (downloadButton) {
|
||||||
downloadButton.remove();
|
downloadButton.remove();
|
||||||
}
|
}
|
||||||
if (downloadPdfButton) {
|
if (downloadPdfButton) {
|
||||||
downloadPdfButton.remove();
|
downloadPdfButton.remove();
|
||||||
}
|
}
|
||||||
if (downloadHtmlButton) {
|
if (downloadPdfButton) {
|
||||||
downloadHtmlButton.remove();
|
downloadMdButton.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function addActionsButtons(actionsArea, TryAgainButton) {
|
function addActionsButtons(actionsArea, TryAgainButton) {
|
||||||
const downloadButton = TryAgainButton.cloneNode(true);
|
const downloadButton = TryAgainButton.cloneNode(true);
|
||||||
|
// Export markdown
|
||||||
|
const exportMd = TryAgainButton.cloneNode(true);
|
||||||
|
exportMd.id = "download-markdown-button";
|
||||||
|
downloadButton.setAttribute("share-ext", "true");
|
||||||
|
exportMd.title = "Export Markdown";
|
||||||
|
exportMd.innerHTML = setIcon('md');
|
||||||
|
exportMd.onclick = () => {
|
||||||
|
exportMarkdown();
|
||||||
|
};
|
||||||
|
actionsArea.appendChild(exportMd);
|
||||||
|
|
||||||
|
// Generate PNG
|
||||||
downloadButton.id = "download-png-button";
|
downloadButton.id = "download-png-button";
|
||||||
downloadButton.setAttribute("share-ext", "true");
|
downloadButton.setAttribute("share-ext", "true");
|
||||||
// downloadButton.innerText = "Generate PNG";
|
|
||||||
downloadButton.title = "Generate PNG";
|
downloadButton.title = "Generate PNG";
|
||||||
downloadButton.innerHTML = setIcon('png');
|
downloadButton.innerHTML = setIcon('png');
|
||||||
downloadButton.onclick = () => {
|
downloadButton.onclick = () => {
|
||||||
downloadThread();
|
downloadThread();
|
||||||
};
|
};
|
||||||
actionsArea.appendChild(downloadButton);
|
actionsArea.appendChild(downloadButton);
|
||||||
|
|
||||||
|
// Generate PDF
|
||||||
const downloadPdfButton = TryAgainButton.cloneNode(true);
|
const downloadPdfButton = TryAgainButton.cloneNode(true);
|
||||||
downloadPdfButton.id = "download-pdf-button";
|
downloadPdfButton.id = "download-pdf-button";
|
||||||
downloadButton.setAttribute("share-ext", "true");
|
downloadButton.setAttribute("share-ext", "true");
|
||||||
// downloadPdfButton.innerText = "Download PDF";
|
|
||||||
downloadPdfButton.title = "Download PDF";
|
downloadPdfButton.title = "Download PDF";
|
||||||
downloadPdfButton.innerHTML = setIcon('pdf');
|
downloadPdfButton.innerHTML = setIcon('pdf');
|
||||||
downloadPdfButton.onclick = () => {
|
downloadPdfButton.onclick = () => {
|
||||||
downloadThread({ as: Format.PDF });
|
downloadThread({ as: Format.PDF });
|
||||||
};
|
};
|
||||||
actionsArea.appendChild(downloadPdfButton);
|
actionsArea.appendChild(downloadPdfButton);
|
||||||
|
}
|
||||||
|
|
||||||
// fix: https://github.com/lencx/ChatGPT/issues/126
|
async function exportMarkdown() {
|
||||||
// const exportHtml = TryAgainButton.cloneNode(true);
|
const data = ExportMD.turndown(document.querySelector("main div>div>div").innerHTML);
|
||||||
// exportHtml.id = "download-html-button";
|
const { id, filename } = getName();
|
||||||
// downloadButton.setAttribute("share-ext", "true");
|
await invoke('save_file', { name: `notes/${id}.md`, content: data });
|
||||||
// // exportHtml.innerText = "Share Link";
|
await invoke('download_list', { pathname: 'chat.notes.json', filename, id, dir: 'notes' });
|
||||||
// exportHtml.title = "Share Link";
|
|
||||||
// exportHtml.innerHTML = setIcon('link');
|
|
||||||
// exportHtml.onclick = () => {
|
|
||||||
// sendRequest();
|
|
||||||
// };
|
|
||||||
// actionsArea.appendChild(exportHtml);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function downloadThread({ as = Format.PNG } = {}) {
|
function downloadThread({ as = Format.PNG } = {}) {
|
||||||
@ -150,16 +162,18 @@ function downloadThread({ as = Format.PNG } = {}) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleImg(imgData) {
|
async function handleImg(imgData) {
|
||||||
const binaryData = atob(imgData.split("base64,")[1]);
|
const binaryData = atob(imgData.split("base64,")[1]);
|
||||||
const data = [];
|
const data = [];
|
||||||
for (let i = 0; i < binaryData.length; i++) {
|
for (let i = 0; i < binaryData.length; i++) {
|
||||||
data.push(binaryData.charCodeAt(i));
|
data.push(binaryData.charCodeAt(i));
|
||||||
}
|
}
|
||||||
invoke('download', { name: `chatgpt-${Date.now()}.png`, blob: Array.from(new Uint8Array(data)) });
|
const { pathname, id, filename } = getName();
|
||||||
|
await invoke('download', { name: `download/img/${id}.png`, blob: data });
|
||||||
|
await invoke('download_list', { pathname, filename, id, dir: 'download' });
|
||||||
}
|
}
|
||||||
|
|
||||||
function handlePdf(imgData, canvas, pixelRatio) {
|
async function handlePdf(imgData, canvas, pixelRatio) {
|
||||||
const { jsPDF } = window.jspdf;
|
const { jsPDF } = window.jspdf;
|
||||||
const orientation = canvas.width > canvas.height ? "l" : "p";
|
const orientation = canvas.width > canvas.height ? "l" : "p";
|
||||||
var pdf = new jsPDF(orientation, "pt", [
|
var pdf = new jsPDF(orientation, "pt", [
|
||||||
@ -169,9 +183,16 @@ function handlePdf(imgData, canvas, pixelRatio) {
|
|||||||
var pdfWidth = pdf.internal.pageSize.getWidth();
|
var pdfWidth = pdf.internal.pageSize.getWidth();
|
||||||
var pdfHeight = pdf.internal.pageSize.getHeight();
|
var pdfHeight = pdf.internal.pageSize.getHeight();
|
||||||
pdf.addImage(imgData, "PNG", 0, 0, pdfWidth, pdfHeight, '', 'FAST');
|
pdf.addImage(imgData, "PNG", 0, 0, pdfWidth, pdfHeight, '', 'FAST');
|
||||||
|
const { pathname, id, filename } = getName();
|
||||||
const data = pdf.__private__.getArrayBuffer(pdf.__private__.buildDocument());
|
const data = pdf.__private__.getArrayBuffer(pdf.__private__.buildDocument());
|
||||||
invoke('download', { name: `chatgpt-${Date.now()}.pdf`, blob: Array.from(new Uint8Array(data)) });
|
await invoke('download', { name: `download/pdf/${id}.pdf`, blob: Array.from(new Uint8Array(data)) });
|
||||||
|
await invoke('download_list', { pathname, filename, id, dir: 'download' });
|
||||||
|
}
|
||||||
|
|
||||||
|
function getName() {
|
||||||
|
const id = uid().toString(36);
|
||||||
|
const name = document.querySelector('nav .overflow-y-auto a.hover\\:bg-gray-800')?.innerText?.trim() || '';
|
||||||
|
return { filename: name ? name : id, id, pathname: 'chat.download.json' };
|
||||||
}
|
}
|
||||||
|
|
||||||
class Elements {
|
class Elements {
|
||||||
@ -187,9 +208,7 @@ class Elements {
|
|||||||
|
|
||||||
// fix: old chat https://github.com/lencx/ChatGPT/issues/185
|
// fix: old chat https://github.com/lencx/ChatGPT/issues/185
|
||||||
if (!this.thread) {
|
if (!this.thread) {
|
||||||
this.thread = document.querySelector(
|
this.thread = document.querySelector("main .overflow-y-auto");
|
||||||
"main .overflow-y-auto"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// h-full overflow-y-auto
|
// h-full overflow-y-auto
|
||||||
@ -245,67 +264,11 @@ class Elements {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectElementByClassPrefix(classPrefix) {
|
|
||||||
const element = document.querySelector(`[class^='${classPrefix}']`);
|
|
||||||
return element;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function sendRequest() {
|
|
||||||
const data = getData();
|
|
||||||
const uploadUrlResponse = await fetch(
|
|
||||||
"https://chatgpt-static.s3.amazonaws.com/url.txt"
|
|
||||||
);
|
|
||||||
const uploadUrl = await uploadUrlResponse.text();
|
|
||||||
fetch(uploadUrl, {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(data),
|
|
||||||
})
|
|
||||||
.then((response) => response.json())
|
|
||||||
.then((data) => {
|
|
||||||
invoke('open_link', { url: data.url });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getData() {
|
|
||||||
const globalCss = getCssFromSheet(
|
|
||||||
document.querySelector("link[rel=stylesheet]").sheet
|
|
||||||
);
|
|
||||||
const localCss =
|
|
||||||
getCssFromSheet(
|
|
||||||
document.querySelector(`style[data-styled][data-styled-version]`).sheet
|
|
||||||
) || "body{}";
|
|
||||||
const data = {
|
|
||||||
main: document.querySelector("main").outerHTML,
|
|
||||||
// css: `${globalCss} /* GLOBAL-LOCAL */ ${localCss}`,
|
|
||||||
globalCss,
|
|
||||||
localCss,
|
|
||||||
};
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getCssFromSheet(sheet) {
|
|
||||||
return Array.from(sheet.cssRules)
|
|
||||||
.map((rule) => rule.cssText)
|
|
||||||
.join("");
|
|
||||||
}
|
|
||||||
|
|
||||||
// run init
|
|
||||||
if (
|
|
||||||
document.readyState === "complete" ||
|
|
||||||
document.readyState === "interactive"
|
|
||||||
) {
|
|
||||||
init();
|
|
||||||
} else {
|
|
||||||
document.addEventListener("DOMContentLoaded", init);
|
|
||||||
}
|
|
||||||
|
|
||||||
function setIcon(type) {
|
function setIcon(type) {
|
||||||
return {
|
return {
|
||||||
link: `<svg class="chatappico" viewBox="0 0 1024 1024"><path d="M1007.382 379.672L655.374 75.702C624.562 49.092 576 70.694 576 112.03v160.106C254.742 275.814 0 340.2 0 644.652c0 122.882 79.162 244.618 166.666 308.264 27.306 19.862 66.222-5.066 56.154-37.262C132.132 625.628 265.834 548.632 576 544.17V720c0 41.4 48.6 62.906 79.374 36.328l352.008-304c22.142-19.124 22.172-53.506 0-72.656z" p-id="8506" fill="currentColor"></path></svg>`,
|
// 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>`,
|
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>`
|
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>`
|
||||||
}[type];
|
}[type];
|
||||||
}
|
}
|
36
src-tauri/src/scripts/markdown.export.js
vendored
Normal file
36
src-tauri/src/scripts/markdown.export.js
vendored
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
var ExportMD = (function () {
|
||||||
|
if (!TurndownService || !turndownPluginGfm) return;
|
||||||
|
const hljsREG = /^.*(hljs).*(language-[a-z0-9]+).*$/i;
|
||||||
|
const gfm = turndownPluginGfm.gfm
|
||||||
|
const turndownService = new TurndownService()
|
||||||
|
.use(gfm)
|
||||||
|
.addRule('code', {
|
||||||
|
filter: (node) => {
|
||||||
|
if (node.nodeName === 'CODE' && hljsREG.test(node.classList.value)) {
|
||||||
|
return 'code';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
replacement: (content, node) => {
|
||||||
|
const classStr = node.getAttribute('class');
|
||||||
|
if (hljsREG.test(classStr)) {
|
||||||
|
const lang = classStr.match(/.*language-(\w+)/)[1];
|
||||||
|
if (lang) {
|
||||||
|
return `\`\`\`${lang}\n${content}\n\`\`\``;
|
||||||
|
}
|
||||||
|
return `\`\`\`\n${content}\n\`\`\``;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.addRule('ignore', {
|
||||||
|
filter: ['button', 'img'],
|
||||||
|
replacement: () => '',
|
||||||
|
})
|
||||||
|
.addRule('table', {
|
||||||
|
filter: 'table',
|
||||||
|
replacement: function(content, node) {
|
||||||
|
return `\`\`\`${content}\n\`\`\``;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return turndownService;
|
||||||
|
}({}));
|
@ -1,6 +1,6 @@
|
|||||||
// *** Core Script - DALL·E 2 Core ***
|
// *** Core Script - DALL·E 2 Core ***
|
||||||
|
|
||||||
async function init() {
|
$(async function () {
|
||||||
const chatConf = await invoke('get_chat_conf') || {};
|
const chatConf = await invoke('get_chat_conf') || {};
|
||||||
if (!chatConf.popup_search) return;
|
if (!chatConf.popup_search) return;
|
||||||
if (!window.FloatingUIDOM) return;
|
if (!window.FloatingUIDOM) return;
|
||||||
@ -71,14 +71,4 @@ async function init() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
})
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
document.readyState === "complete" ||
|
|
||||||
document.readyState === "interactive"
|
|
||||||
) {
|
|
||||||
init();
|
|
||||||
} else {
|
|
||||||
document.addEventListener("DOMContentLoaded", init);
|
|
||||||
}
|
|
@ -230,3 +230,23 @@ pub async fn silent_install(app: AppHandle<Wry>, update: UpdateResponse<Wry>) ->
|
|||||||
|
|
||||||
Ok(())
|
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<Item = serde_json::Value>,
|
||||||
|
key: &str,
|
||||||
|
map: &mut HashMap<String, serde_json::Value>,
|
||||||
|
) {
|
||||||
|
for v in vec {
|
||||||
|
if let Some(kval) = v.get(key).and_then(serde_json::Value::as_str) {
|
||||||
|
map.insert(kval.to_string(), v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
2
src-tauri/src/vendors/jq.js
vendored
Normal file
2
src-tauri/src/vendors/jq.js
vendored
Normal file
File diff suppressed because one or more lines are too long
164
src-tauri/src/vendors/turndown-plugin-gfm.js
vendored
Normal file
164
src-tauri/src/vendors/turndown-plugin-gfm.js
vendored
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
var turndownPluginGfm = (function (exports) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var highlightRegExp = /highlight-(?:text|source)-([a-z0-9]+)/;
|
||||||
|
|
||||||
|
function highlightedCodeBlock (turndownService) {
|
||||||
|
turndownService.addRule('highlightedCodeBlock', {
|
||||||
|
filter: function (node) {
|
||||||
|
var firstChild = node.firstChild;
|
||||||
|
return (
|
||||||
|
node.nodeName === 'DIV' &&
|
||||||
|
highlightRegExp.test(node.className) &&
|
||||||
|
firstChild &&
|
||||||
|
firstChild.nodeName === 'PRE'
|
||||||
|
)
|
||||||
|
},
|
||||||
|
replacement: function (content, node, options) {
|
||||||
|
var className = node.className || '';
|
||||||
|
var language = (className.match(highlightRegExp) || [null, ''])[1];
|
||||||
|
|
||||||
|
return (
|
||||||
|
'\n\n' + options.fence + language + '\n' +
|
||||||
|
node.firstChild.textContent +
|
||||||
|
'\n' + options.fence + '\n\n'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function strikethrough (turndownService) {
|
||||||
|
turndownService.addRule('strikethrough', {
|
||||||
|
filter: ['del', 's', 'strike'],
|
||||||
|
replacement: function (content) {
|
||||||
|
return '~' + content + '~'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var indexOf = Array.prototype.indexOf;
|
||||||
|
var every = Array.prototype.every;
|
||||||
|
var rules = {};
|
||||||
|
|
||||||
|
rules.tableCell = {
|
||||||
|
filter: ['th', 'td'],
|
||||||
|
replacement: function (content, node) {
|
||||||
|
return cell(content, node)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
rules.tableRow = {
|
||||||
|
filter: 'tr',
|
||||||
|
replacement: function (content, node) {
|
||||||
|
var borderCells = '';
|
||||||
|
var alignMap = { left: ':--', right: '--:', center: ':-:' };
|
||||||
|
|
||||||
|
if (isHeadingRow(node)) {
|
||||||
|
for (var i = 0; i < node.childNodes.length; i++) {
|
||||||
|
var border = '---';
|
||||||
|
var align = (
|
||||||
|
node.childNodes[i].getAttribute('align') || ''
|
||||||
|
).toLowerCase();
|
||||||
|
|
||||||
|
if (align) border = alignMap[align] || border;
|
||||||
|
|
||||||
|
borderCells += cell(border, node.childNodes[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return '\n' + content + (borderCells ? '\n' + borderCells : '')
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
rules.table = {
|
||||||
|
// Only convert tables with a heading row.
|
||||||
|
// Tables with no heading row are kept using `keep` (see below).
|
||||||
|
filter: function (node) {
|
||||||
|
return node.nodeName === 'TABLE' && isHeadingRow(node.rows[0])
|
||||||
|
},
|
||||||
|
|
||||||
|
replacement: function (content) {
|
||||||
|
// Ensure there are no blank lines
|
||||||
|
content = content.replace('\n\n', '\n');
|
||||||
|
return '\n\n' + content + '\n\n'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
rules.tableSection = {
|
||||||
|
filter: ['thead', 'tbody', 'tfoot'],
|
||||||
|
replacement: function (content) {
|
||||||
|
return content
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// A tr is a heading row if:
|
||||||
|
// - the parent is a THEAD
|
||||||
|
// - or if its the first child of the TABLE or the first TBODY (possibly
|
||||||
|
// following a blank THEAD)
|
||||||
|
// - and every cell is a TH
|
||||||
|
function isHeadingRow (tr) {
|
||||||
|
var parentNode = tr.parentNode;
|
||||||
|
return (
|
||||||
|
parentNode.nodeName === 'THEAD' ||
|
||||||
|
(
|
||||||
|
parentNode.firstChild === tr &&
|
||||||
|
(parentNode.nodeName === 'TABLE' || isFirstTbody(parentNode)) &&
|
||||||
|
every.call(tr.childNodes, function (n) { return n.nodeName === 'TH' })
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function isFirstTbody (element) {
|
||||||
|
var previousSibling = element.previousSibling;
|
||||||
|
return (
|
||||||
|
element.nodeName === 'TBODY' && (
|
||||||
|
!previousSibling ||
|
||||||
|
(
|
||||||
|
previousSibling.nodeName === 'THEAD' &&
|
||||||
|
/^\s*$/i.test(previousSibling.textContent)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function cell (content, node) {
|
||||||
|
var index = indexOf.call(node.parentNode.childNodes, node);
|
||||||
|
var prefix = ' ';
|
||||||
|
if (index === 0) prefix = '| ';
|
||||||
|
return prefix + content + ' |'
|
||||||
|
}
|
||||||
|
|
||||||
|
function tables (turndownService) {
|
||||||
|
turndownService.keep(function (node) {
|
||||||
|
return node.nodeName === 'TABLE' && !isHeadingRow(node.rows[0])
|
||||||
|
});
|
||||||
|
for (var key in rules) turndownService.addRule(key, rules[key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function taskListItems (turndownService) {
|
||||||
|
turndownService.addRule('taskListItems', {
|
||||||
|
filter: function (node) {
|
||||||
|
return node.type === 'checkbox' && node.parentNode.nodeName === 'LI'
|
||||||
|
},
|
||||||
|
replacement: function (content, node) {
|
||||||
|
return (node.checked ? '[x]' : '[ ]') + ' '
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function gfm (turndownService) {
|
||||||
|
turndownService.use([
|
||||||
|
highlightedCodeBlock,
|
||||||
|
strikethrough,
|
||||||
|
tables,
|
||||||
|
taskListItems
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.gfm = gfm;
|
||||||
|
exports.highlightedCodeBlock = highlightedCodeBlock;
|
||||||
|
exports.strikethrough = strikethrough;
|
||||||
|
exports.tables = tables;
|
||||||
|
exports.taskListItems = taskListItems;
|
||||||
|
|
||||||
|
return exports;
|
||||||
|
}({}));
|
1
src-tauri/src/vendors/turndown.js
vendored
Normal file
1
src-tauri/src/vendors/turndown.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -7,7 +7,7 @@
|
|||||||
},
|
},
|
||||||
"package": {
|
"package": {
|
||||||
"productName": "ChatGPT",
|
"productName": "ChatGPT",
|
||||||
"version": "0.8.1"
|
"version": "0.9.0"
|
||||||
},
|
},
|
||||||
"tauri": {
|
"tauri": {
|
||||||
"allowlist": {
|
"allowlist": {
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
import { useState, useCallback } from 'react';
|
import { FC, useState, useCallback } from 'react';
|
||||||
|
import { Input } from 'antd';
|
||||||
|
|
||||||
|
import { DISABLE_AUTO_COMPLETE } from '@/utils';
|
||||||
|
|
||||||
export default function useColumns(columns: any[] = []) {
|
export default function useColumns(columns: any[] = []) {
|
||||||
const [opType, setOpType] = useState('');
|
const [opType, setOpType] = useState('');
|
||||||
@ -42,3 +45,39 @@ export default function useColumns(columns: any[] = []) {
|
|||||||
opExtra,
|
opExtra,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface EditRowProps {
|
||||||
|
rowKey: string;
|
||||||
|
row: Record<string, any>;
|
||||||
|
actions: any;
|
||||||
|
}
|
||||||
|
export const EditRow: FC<EditRowProps> = ({ rowKey, row, actions }) => {
|
||||||
|
const [isEdit, setEdit] = useState(false);
|
||||||
|
const [val, setVal] = useState(row[rowKey]);
|
||||||
|
const handleEdit = () => {
|
||||||
|
setEdit(true);
|
||||||
|
};
|
||||||
|
const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||||
|
setVal(e.target.value)
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSave = () => {
|
||||||
|
setEdit(false);
|
||||||
|
row[rowKey] = val;
|
||||||
|
actions?.setRecord(row, 'rowedit')
|
||||||
|
};
|
||||||
|
|
||||||
|
return isEdit
|
||||||
|
? (
|
||||||
|
<Input.TextArea
|
||||||
|
value={val}
|
||||||
|
rows={1}
|
||||||
|
onChange={handleChange}
|
||||||
|
{...DISABLE_AUTO_COMPLETE}
|
||||||
|
onPressEnter={handleSave}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
: (
|
||||||
|
<div className='rowedit' onClick={handleEdit}>{val}</div>
|
||||||
|
);
|
||||||
|
};
|
23
src/hooks/useJson.ts
vendored
Normal file
23
src/hooks/useJson.ts
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
import { readJSON, writeJSON } from '@/utils';
|
||||||
|
import useInit from '@/hooks/useInit';
|
||||||
|
|
||||||
|
export default function useJson<T>(file: string) {
|
||||||
|
const [json, setData] = useState<T>();
|
||||||
|
|
||||||
|
const refreshJson = async () => {
|
||||||
|
const data = await readJSON(file);
|
||||||
|
setData(data);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateJson = async (data: any) => {
|
||||||
|
await writeJSON(file, data);
|
||||||
|
await refreshJson();
|
||||||
|
};
|
||||||
|
|
||||||
|
useInit(refreshJson);
|
||||||
|
|
||||||
|
return { json, refreshJson, updateJson };
|
||||||
|
}
|
33
src/hooks/useTable.tsx
vendored
33
src/hooks/useTable.tsx
vendored
@ -4,14 +4,35 @@ import type { TableRowSelection } from 'antd/es/table/interface';
|
|||||||
|
|
||||||
import { safeKey } from '@/hooks/useData';
|
import { safeKey } from '@/hooks/useData';
|
||||||
|
|
||||||
export default function useTableRowSelection() {
|
type rowSelectionOptions = {
|
||||||
|
key: 'id' | string;
|
||||||
|
rowType: 'id' | 'row' | 'all';
|
||||||
|
}
|
||||||
|
export function useTableRowSelection(options: Partial<rowSelectionOptions> = {}) {
|
||||||
|
const { key = 'id', rowType = 'id' } = options;
|
||||||
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
|
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
|
||||||
const [selectedRowIDs, setSelectedRowIDs] = useState<string[]>([]);
|
const [selectedRowIDs, setSelectedRowIDs] = useState<string[]>([]);
|
||||||
|
const [selectedRows, setSelectedRows] = useState<Record<string|symbol, any>[]>([]);
|
||||||
|
|
||||||
const onSelectChange = (newSelectedRowKeys: React.Key[], selectedRows: Record<string|symbol, any>) => {
|
const onSelectChange = (newSelectedRowKeys: React.Key[], newSelectedRows: Record<string|symbol, any>[]) => {
|
||||||
const keys = selectedRows.map((i: any) => i[safeKey]);
|
const keys = newSelectedRows.map((i: any) => i[safeKey] || i[key]);
|
||||||
setSelectedRowIDs(keys);
|
|
||||||
setSelectedRowKeys(newSelectedRowKeys);
|
setSelectedRowKeys(newSelectedRowKeys);
|
||||||
|
if (rowType === 'id') {
|
||||||
|
setSelectedRowIDs(keys);
|
||||||
|
}
|
||||||
|
if (rowType === 'row') {
|
||||||
|
setSelectedRows(newSelectedRows);
|
||||||
|
}
|
||||||
|
if (rowType === 'all') {
|
||||||
|
setSelectedRowIDs(keys);
|
||||||
|
setSelectedRows(newSelectedRows);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const rowReset = () => {
|
||||||
|
setSelectedRowKeys([]);
|
||||||
|
setSelectedRowIDs([]);
|
||||||
|
setSelectedRows([]);
|
||||||
};
|
};
|
||||||
|
|
||||||
const rowSelection: TableRowSelection<Record<string, any>> = {
|
const rowSelection: TableRowSelection<Record<string, any>> = {
|
||||||
@ -24,14 +45,14 @@ export default function useTableRowSelection() {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
return { rowSelection, selectedRowIDs };
|
return { rowSelection, selectedRowIDs, selectedRows, rowReset };
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TABLE_PAGINATION = {
|
export const TABLE_PAGINATION = {
|
||||||
hideOnSinglePage: true,
|
hideOnSinglePage: true,
|
||||||
showSizeChanger: true,
|
showSizeChanger: true,
|
||||||
showQuickJumper: true,
|
showQuickJumper: true,
|
||||||
defaultPageSize: 5,
|
defaultPageSize: 10,
|
||||||
pageSizeOptions: [5, 10, 15, 20],
|
pageSizeOptions: [5, 10, 15, 20],
|
||||||
showTotal: (total: number) => <span>Total {total} items</span>,
|
showTotal: (total: number) => <span>Total {total} items</span>,
|
||||||
};
|
};
|
11
src/layout/index.tsx
vendored
11
src/layout/index.tsx
vendored
@ -1,5 +1,5 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import {Layout, Menu, Tooltip, ConfigProvider, theme, Tag } from 'antd';
|
import { Layout, Menu, Tooltip, ConfigProvider, theme, Tag } from 'antd';
|
||||||
import { SyncOutlined } from '@ant-design/icons';
|
import { SyncOutlined } from '@ant-design/icons';
|
||||||
import { useNavigate, useLocation } from 'react-router-dom';
|
import { useNavigate, useLocation } from 'react-router-dom';
|
||||||
import { getName, getVersion } from '@tauri-apps/api/app';
|
import { getName, getVersion } from '@tauri-apps/api/app';
|
||||||
@ -29,11 +29,13 @@ export default function ChatLayout() {
|
|||||||
await invoke('run_check_update', { silent: false, hasMsg: true });
|
await invoke('run_check_update', { silent: false, hasMsg: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isDark = appInfo.appTheme === "dark";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ConfigProvider theme={{algorithm: appInfo.appTheme === "dark" ? theme.darkAlgorithm : theme.defaultAlgorithm}}>
|
<ConfigProvider theme={{ algorithm: isDark ? theme.darkAlgorithm : theme.defaultAlgorithm }}>
|
||||||
<Layout style={{ minHeight: '100vh' }} hasSider>
|
<Layout style={{ minHeight: '100vh' }} hasSider>
|
||||||
<Sider
|
<Sider
|
||||||
theme={appInfo.appTheme === "dark" ? "dark" : "light"}
|
theme={isDark ? "dark" : "light"}
|
||||||
collapsible
|
collapsible
|
||||||
collapsed={collapsed}
|
collapsed={collapsed}
|
||||||
onCollapse={(value) => setCollapsed(value)}
|
onCollapse={(value) => setCollapsed(value)}
|
||||||
@ -78,7 +80,8 @@ export default function ChatLayout() {
|
|||||||
<Routes />
|
<Routes />
|
||||||
</Content>
|
</Content>
|
||||||
<Footer style={{ textAlign: 'center' }}>
|
<Footer style={{ textAlign: 'center' }}>
|
||||||
<a href="https://github.com/lencx/chatgpt" target="_blank">ChatGPT Desktop Application</a> ©2022 Created by lencx</Footer>
|
<a href="https://github.com/lencx/chatgpt" target="_blank">ChatGPT Desktop Application</a> ©2022 Created by lencx
|
||||||
|
</Footer>
|
||||||
</Layout>
|
</Layout>
|
||||||
</Layout>
|
</Layout>
|
||||||
</ConfigProvider>
|
</ConfigProvider>
|
||||||
|
20
src/main.scss
vendored
20
src/main.scss
vendored
@ -31,10 +31,27 @@ html, body {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
-webkit-line-clamp: 3;
|
-webkit-line-clamp: 2;
|
||||||
-webkit-box-orient: vertical;
|
-webkit-box-orient: vertical;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ellipsis-line {
|
||||||
|
display: inline-block;
|
||||||
|
width: 180px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rowedit {
|
||||||
|
padding: 2px 5px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
box-shadow: 0 0 2px rgba(237, 122, 60, 0.8);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.chat-add-btn {
|
.chat-add-btn {
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
}
|
}
|
||||||
@ -51,6 +68,7 @@ html, body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.chat-file-path,
|
||||||
.chat-sync-path {
|
.chat-sync-path {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
27
src/routes.tsx
vendored
27
src/routes.tsx
vendored
@ -1,10 +1,12 @@
|
|||||||
import { useRoutes } from 'react-router-dom';
|
import { useRoutes } from 'react-router-dom';
|
||||||
import {
|
import {
|
||||||
DesktopOutlined,
|
SettingOutlined,
|
||||||
BulbOutlined,
|
BulbOutlined,
|
||||||
SyncOutlined,
|
SyncOutlined,
|
||||||
FileSyncOutlined,
|
FileSyncOutlined,
|
||||||
UserOutlined,
|
UserOutlined,
|
||||||
|
DownloadOutlined,
|
||||||
|
FormOutlined,
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import type { MenuProps } from 'antd';
|
import type { MenuProps } from 'antd';
|
||||||
|
|
||||||
@ -13,6 +15,8 @@ import UserCustom from '@/view/model/UserCustom';
|
|||||||
import SyncPrompts from '@/view/model/SyncPrompts';
|
import SyncPrompts from '@/view/model/SyncPrompts';
|
||||||
import SyncCustom from '@/view/model/SyncCustom';
|
import SyncCustom from '@/view/model/SyncCustom';
|
||||||
import SyncRecord from '@/view/model/SyncRecord';
|
import SyncRecord from '@/view/model/SyncRecord';
|
||||||
|
import Download from '@/view/download';
|
||||||
|
import Notes from '@/view/notes';
|
||||||
|
|
||||||
export type ChatRouteMetaObject = {
|
export type ChatRouteMetaObject = {
|
||||||
label: string;
|
label: string;
|
||||||
@ -33,7 +37,15 @@ export const routes: Array<ChatRouteObject> = [
|
|||||||
element: <General />,
|
element: <General />,
|
||||||
meta: {
|
meta: {
|
||||||
label: 'General',
|
label: 'General',
|
||||||
icon: <DesktopOutlined />,
|
icon: <SettingOutlined />,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/notes',
|
||||||
|
element: <Notes />,
|
||||||
|
meta: {
|
||||||
|
label: 'Notes',
|
||||||
|
icon: <FormOutlined />,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -51,6 +63,7 @@ export const routes: Array<ChatRouteObject> = [
|
|||||||
icon: <UserOutlined />,
|
icon: <UserOutlined />,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
// --- Sync
|
||||||
{
|
{
|
||||||
path: 'sync-prompts',
|
path: 'sync-prompts',
|
||||||
element: <SyncPrompts />,
|
element: <SyncPrompts />,
|
||||||
@ -72,7 +85,15 @@ export const routes: Array<ChatRouteObject> = [
|
|||||||
element: <SyncRecord />,
|
element: <SyncRecord />,
|
||||||
hideMenu: true,
|
hideMenu: true,
|
||||||
},
|
},
|
||||||
]
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'download',
|
||||||
|
element: <Download />,
|
||||||
|
meta: {
|
||||||
|
label: 'Download',
|
||||||
|
icon: <DownloadOutlined />,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
2
src/utils.ts
vendored
2
src/utils.ts
vendored
@ -4,6 +4,8 @@ import dayjs from 'dayjs';
|
|||||||
|
|
||||||
export const CHAT_MODEL_JSON = 'chat.model.json';
|
export const CHAT_MODEL_JSON = 'chat.model.json';
|
||||||
export const CHAT_MODEL_CMD_JSON = 'chat.model.cmd.json';
|
export const CHAT_MODEL_CMD_JSON = 'chat.model.cmd.json';
|
||||||
|
export const CHAT_DOWNLOAD_JSON = 'chat.download.json';
|
||||||
|
export const CHAT_NOTES_JSON = 'chat.notes.json';
|
||||||
export const CHAT_PROMPTS_CSV = 'chat.prompts.csv';
|
export const CHAT_PROMPTS_CSV = 'chat.prompts.csv';
|
||||||
export const GITHUB_PROMPTS_CSV_URL = 'https://raw.githubusercontent.com/f/awesome-chatgpt-prompts/main/prompts.csv';
|
export const GITHUB_PROMPTS_CSV_URL = 'https://raw.githubusercontent.com/f/awesome-chatgpt-prompts/main/prompts.csv';
|
||||||
export const DISABLE_AUTO_COMPLETE = {
|
export const DISABLE_AUTO_COMPLETE = {
|
||||||
|
80
src/view/download/config.tsx
vendored
Normal file
80
src/view/download/config.tsx
vendored
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import { Tag, Space, Popconfirm } from 'antd';
|
||||||
|
import { path, shell } from '@tauri-apps/api';
|
||||||
|
|
||||||
|
import { EditRow } from '@/hooks/useColumns';
|
||||||
|
|
||||||
|
import useInit from '@/hooks/useInit';
|
||||||
|
import { fmtDate, chatRoot } from '@/utils';
|
||||||
|
|
||||||
|
const colorMap: any = {
|
||||||
|
pdf: 'blue',
|
||||||
|
png: 'orange',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const downloadColumns = () => [
|
||||||
|
{
|
||||||
|
title: 'Name',
|
||||||
|
dataIndex: 'name',
|
||||||
|
fixed: 'left',
|
||||||
|
key: 'name',
|
||||||
|
width: 240,
|
||||||
|
render: (_: string, row: any, actions: any) => (
|
||||||
|
<EditRow rowKey="name" row={row} actions={actions} />
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Extension',
|
||||||
|
dataIndex: 'ext',
|
||||||
|
key: 'ext',
|
||||||
|
width: 120,
|
||||||
|
render: (v: string) => <Tag color={colorMap[v]}>{v}</Tag>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Path',
|
||||||
|
dataIndex: 'path',
|
||||||
|
key: 'path',
|
||||||
|
width: 200,
|
||||||
|
render: (_: string, row: any) => <RenderPath row={row} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Created',
|
||||||
|
dataIndex: 'created',
|
||||||
|
key: 'created',
|
||||||
|
width: 150,
|
||||||
|
render: fmtDate,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Action',
|
||||||
|
fixed: 'right',
|
||||||
|
width: 150,
|
||||||
|
render: (_: any, row: any, actions: any) => {
|
||||||
|
return (
|
||||||
|
<Space>
|
||||||
|
<a onClick={() => actions.setRecord(row, 'preview')}>Preview</a>
|
||||||
|
<Popconfirm
|
||||||
|
title="Are you sure to delete this file?"
|
||||||
|
onConfirm={() => actions.setRecord(row, 'delete')}
|
||||||
|
okText="Yes"
|
||||||
|
cancelText="No"
|
||||||
|
>
|
||||||
|
<a>Delete</a>
|
||||||
|
</Popconfirm>
|
||||||
|
</Space>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const RenderPath = ({ row }: any) => {
|
||||||
|
const [filePath, setFilePath] = useState('');
|
||||||
|
useInit(async () => {
|
||||||
|
setFilePath(await getPath(row));
|
||||||
|
})
|
||||||
|
return <a onClick={() => shell.open(filePath)}>{filePath}</a>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getPath = async (row: any) => {
|
||||||
|
const isImg = ['png'].includes(row?.ext);
|
||||||
|
return await path.join(await chatRoot(), 'download', isImg ? 'img' : row.ext, row.id) + `.${row.ext}`;
|
||||||
|
}
|
145
src/view/download/index.tsx
vendored
Normal file
145
src/view/download/index.tsx
vendored
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { Table, Modal, Popconfirm, Button, message } from 'antd';
|
||||||
|
import { invoke, path, shell, fs } from '@tauri-apps/api';
|
||||||
|
|
||||||
|
import useInit from '@/hooks/useInit';
|
||||||
|
import useJson from '@/hooks/useJson';
|
||||||
|
import useData from '@/hooks/useData';
|
||||||
|
import useColumns from '@/hooks/useColumns';
|
||||||
|
import { useTableRowSelection, TABLE_PAGINATION } from '@/hooks/useTable';
|
||||||
|
import { chatRoot, CHAT_DOWNLOAD_JSON } from '@/utils';
|
||||||
|
import { downloadColumns } from './config';
|
||||||
|
|
||||||
|
function renderFile(buff: Uint8Array, type: string) {
|
||||||
|
const renderType = {
|
||||||
|
pdf: 'application/pdf',
|
||||||
|
png: 'image/png',
|
||||||
|
}[type];
|
||||||
|
return URL.createObjectURL(new Blob([buff], { type: renderType }));
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Download() {
|
||||||
|
const [downloadPath, setDownloadPath] = useState('');
|
||||||
|
const [source, setSource] = useState('');
|
||||||
|
const [isVisible, setVisible] = useState(false);
|
||||||
|
const { opData, opInit, opReplace, opSafeKey } = useData([]);
|
||||||
|
const { columns, ...opInfo } = useColumns(downloadColumns());
|
||||||
|
const { rowSelection, selectedRows, rowReset } = useTableRowSelection({ rowType: 'row' });
|
||||||
|
const { json, refreshJson, updateJson } = useJson<any[]>(CHAT_DOWNLOAD_JSON);
|
||||||
|
const selectedItems = rowSelection.selectedRowKeys || [];
|
||||||
|
|
||||||
|
useInit(async () => {
|
||||||
|
const file = await path.join(await chatRoot(), CHAT_DOWNLOAD_JSON);
|
||||||
|
setDownloadPath(file);
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!json || json.length <= 0) return;
|
||||||
|
opInit(json);
|
||||||
|
}, [json?.length]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!opInfo.opType) return;
|
||||||
|
(async () => {
|
||||||
|
const record = opInfo?.opRecord;
|
||||||
|
const isImg = ['png'].includes(record?.ext);
|
||||||
|
const file = await path.join(await chatRoot(), 'download', isImg ? 'img' : record?.ext, `${record?.id}.${record?.ext}`);
|
||||||
|
if (opInfo.opType === 'preview') {
|
||||||
|
const data = await fs.readBinaryFile(file);
|
||||||
|
const sourceData = renderFile(data, record?.ext);
|
||||||
|
setSource(sourceData);
|
||||||
|
setVisible(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (opInfo.opType === 'delete') {
|
||||||
|
await fs.removeFile(file);
|
||||||
|
await handleRefresh();
|
||||||
|
}
|
||||||
|
if (opInfo.opType === 'rowedit') {
|
||||||
|
const data = opReplace(opInfo?.opRecord?.[opSafeKey], opInfo?.opRecord);
|
||||||
|
await updateJson(data);
|
||||||
|
message.success('Name has been changed!');
|
||||||
|
}
|
||||||
|
opInfo.resetRecord();
|
||||||
|
})()
|
||||||
|
}, [opInfo.opType])
|
||||||
|
|
||||||
|
const handleDelete = async () => {
|
||||||
|
if (opData?.length === selectedRows.length) {
|
||||||
|
const downloadDir = await path.join(await chatRoot(), 'download');
|
||||||
|
await fs.removeDir(downloadDir, { recursive: true });
|
||||||
|
await handleRefresh();
|
||||||
|
rowReset();
|
||||||
|
message.success('All files have been cleared!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rows = selectedRows.map(async (i) => {
|
||||||
|
const isImg = ['png'].includes(i?.ext);
|
||||||
|
const file = await path.join(await chatRoot(), 'download', isImg ? 'img' : i?.ext, `${i?.id}.${i?.ext}`);
|
||||||
|
await fs.removeFile(file);
|
||||||
|
return file;
|
||||||
|
})
|
||||||
|
Promise.all(rows).then(async () => {
|
||||||
|
await handleRefresh();
|
||||||
|
message.success('All files selected are cleared!');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRefresh = async () => {
|
||||||
|
await invoke('download_list', { pathname: CHAT_DOWNLOAD_JSON, dir: 'download' });
|
||||||
|
const data = await refreshJson();
|
||||||
|
opInit(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
setVisible(false);
|
||||||
|
opInfo.resetRecord();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="chat-table-btns">
|
||||||
|
<div>
|
||||||
|
{selectedItems.length > 0 && (
|
||||||
|
<>
|
||||||
|
<Popconfirm
|
||||||
|
overlayStyle={{ width: 250 }}
|
||||||
|
title="Files cannot be recovered after deletion, are you sure you want to delete them?"
|
||||||
|
placement="topLeft"
|
||||||
|
onConfirm={handleDelete}
|
||||||
|
okText="Yes"
|
||||||
|
cancelText="No"
|
||||||
|
>
|
||||||
|
<Button>Batch delete</Button>
|
||||||
|
</Popconfirm>
|
||||||
|
<span className="num">Selected {selectedItems.length} items</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="chat-table-tip">
|
||||||
|
<div className="chat-file-path">
|
||||||
|
<div>PATH: <a onClick={() => shell.open(downloadPath)} title={downloadPath}>{downloadPath}</a></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Table
|
||||||
|
rowKey="id"
|
||||||
|
columns={columns}
|
||||||
|
scroll={{ x: 800 }}
|
||||||
|
dataSource={opData}
|
||||||
|
rowSelection={rowSelection}
|
||||||
|
pagination={TABLE_PAGINATION}
|
||||||
|
/>
|
||||||
|
<Modal
|
||||||
|
open={isVisible}
|
||||||
|
title={<div>{opInfo?.opRecord?.name || ''}</div>}
|
||||||
|
onCancel={handleCancel}
|
||||||
|
footer={false}
|
||||||
|
destroyOnClose
|
||||||
|
>
|
||||||
|
<img style={{ maxWidth: '100%' }} src={source} />
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
5
src/view/model/SyncPrompts/config.tsx
vendored
5
src/view/model/SyncPrompts/config.tsx
vendored
@ -1,4 +1,4 @@
|
|||||||
import { Switch, Tag, Tooltip } from 'antd';
|
import { Table, Switch, Tag } from 'antd';
|
||||||
|
|
||||||
import { genCmd } from '@/utils';
|
import { genCmd } from '@/utils';
|
||||||
|
|
||||||
@ -35,13 +35,14 @@ export const syncColumns = () => [
|
|||||||
<Switch checked={v} onChange={(v) => action.setRecord({ ...row, enable: v }, 'enable')} />
|
<Switch checked={v} onChange={(v) => action.setRecord({ ...row, enable: v }, 'enable')} />
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
Table.EXPAND_COLUMN,
|
||||||
{
|
{
|
||||||
title: 'Prompt',
|
title: 'Prompt',
|
||||||
dataIndex: 'prompt',
|
dataIndex: 'prompt',
|
||||||
key: 'prompt',
|
key: 'prompt',
|
||||||
// width: 300,
|
// width: 300,
|
||||||
render: (v: string) => (
|
render: (v: string) => (
|
||||||
<Tooltip overlayInnerStyle={{ width: 350 }} title={v}><span className="chat-prompts-val">{v}</span></Tooltip>
|
<span className="chat-prompts-val">{v}</span>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
5
src/view/model/SyncPrompts/index.tsx
vendored
5
src/view/model/SyncPrompts/index.tsx
vendored
@ -6,7 +6,7 @@ import useInit from '@/hooks/useInit';
|
|||||||
import useData from '@/hooks/useData';
|
import useData from '@/hooks/useData';
|
||||||
import useColumns from '@/hooks/useColumns';
|
import useColumns from '@/hooks/useColumns';
|
||||||
import useChatModel, { useCacheModel } from '@/hooks/useChatModel';
|
import useChatModel, { useCacheModel } from '@/hooks/useChatModel';
|
||||||
import useTable, { TABLE_PAGINATION } from '@/hooks/useTable';
|
import { useTableRowSelection, TABLE_PAGINATION } from '@/hooks/useTable';
|
||||||
import { fmtDate, chatRoot } from '@/utils';
|
import { fmtDate, chatRoot } from '@/utils';
|
||||||
import { syncColumns } from './config';
|
import { syncColumns } from './config';
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
@ -14,7 +14,7 @@ import './index.scss';
|
|||||||
const promptsURL = 'https://github.com/f/awesome-chatgpt-prompts/blob/main/prompts.csv';
|
const promptsURL = 'https://github.com/f/awesome-chatgpt-prompts/blob/main/prompts.csv';
|
||||||
|
|
||||||
export default function SyncPrompts() {
|
export default function SyncPrompts() {
|
||||||
const { rowSelection, selectedRowIDs } = useTable();
|
const { rowSelection, selectedRowIDs } = useTableRowSelection();
|
||||||
const [jsonPath, setJsonPath] = useState('');
|
const [jsonPath, setJsonPath] = useState('');
|
||||||
const { modelJson, modelSet } = useChatModel('sync_prompts');
|
const { modelJson, modelSet } = useChatModel('sync_prompts');
|
||||||
const { modelCacheJson, modelCacheSet } = useCacheModel(jsonPath);
|
const { modelCacheJson, modelCacheSet } = useCacheModel(jsonPath);
|
||||||
@ -93,6 +93,7 @@ export default function SyncPrompts() {
|
|||||||
dataSource={opData}
|
dataSource={opData}
|
||||||
rowSelection={rowSelection}
|
rowSelection={rowSelection}
|
||||||
pagination={TABLE_PAGINATION}
|
pagination={TABLE_PAGINATION}
|
||||||
|
expandable={{expandedRowRender: (record) => <div style={{ padding: 10 }}>{record.prompt}</div>}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
5
src/view/model/SyncRecord/config.tsx
vendored
5
src/view/model/SyncRecord/config.tsx
vendored
@ -1,4 +1,4 @@
|
|||||||
import { Switch, Tag, Tooltip } from 'antd';
|
import { Switch, Tag, Table } from 'antd';
|
||||||
|
|
||||||
import { genCmd } from '@/utils';
|
import { genCmd } from '@/utils';
|
||||||
|
|
||||||
@ -37,13 +37,14 @@ export const syncColumns = () => [
|
|||||||
<Switch checked={v} onChange={(v) => action.setRecord({ ...row, enable: v }, 'enable')} />
|
<Switch checked={v} onChange={(v) => action.setRecord({ ...row, enable: v }, 'enable')} />
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
Table.EXPAND_COLUMN,
|
||||||
{
|
{
|
||||||
title: 'Prompt',
|
title: 'Prompt',
|
||||||
dataIndex: 'prompt',
|
dataIndex: 'prompt',
|
||||||
key: 'prompt',
|
key: 'prompt',
|
||||||
// width: 300,
|
// width: 300,
|
||||||
render: (v: string) => (
|
render: (v: string) => (
|
||||||
<Tooltip overlayInnerStyle={{ width: 350 }} title={v}><span className="chat-prompts-val">{v}</span></Tooltip>
|
<span className="chat-prompts-val">{v}</span>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
5
src/view/model/SyncRecord/index.tsx
vendored
5
src/view/model/SyncRecord/index.tsx
vendored
@ -7,7 +7,7 @@ import { shell, path } from '@tauri-apps/api';
|
|||||||
import useColumns from '@/hooks/useColumns';
|
import useColumns from '@/hooks/useColumns';
|
||||||
import useData from '@/hooks/useData';
|
import useData from '@/hooks/useData';
|
||||||
import { useCacheModel } from '@/hooks/useChatModel';
|
import { useCacheModel } from '@/hooks/useChatModel';
|
||||||
import useTable, { TABLE_PAGINATION } from '@/hooks/useTable';
|
import { useTableRowSelection, TABLE_PAGINATION } from '@/hooks/useTable';
|
||||||
import { fmtDate, chatRoot } from '@/utils';
|
import { fmtDate, chatRoot } from '@/utils';
|
||||||
import { getPath } from '@/view/model/SyncCustom/config';
|
import { getPath } from '@/view/model/SyncCustom/config';
|
||||||
import { syncColumns } from './config';
|
import { syncColumns } from './config';
|
||||||
@ -19,7 +19,7 @@ export default function SyncRecord() {
|
|||||||
const [jsonPath, setJsonPath] = useState('');
|
const [jsonPath, setJsonPath] = useState('');
|
||||||
const state = location?.state;
|
const state = location?.state;
|
||||||
|
|
||||||
const { rowSelection, selectedRowIDs } = useTable();
|
const { rowSelection, selectedRowIDs } = useTableRowSelection();
|
||||||
const { modelCacheJson, modelCacheSet } = useCacheModel(jsonPath);
|
const { modelCacheJson, modelCacheSet } = useCacheModel(jsonPath);
|
||||||
const { opData, opInit, opReplace, opReplaceItems, opSafeKey } = useData([]);
|
const { opData, opInit, opReplace, opReplaceItems, opSafeKey } = useData([]);
|
||||||
const { columns, ...opInfo } = useColumns(syncColumns());
|
const { columns, ...opInfo } = useColumns(syncColumns());
|
||||||
@ -79,6 +79,7 @@ export default function SyncRecord() {
|
|||||||
dataSource={opData}
|
dataSource={opData}
|
||||||
rowSelection={rowSelection}
|
rowSelection={rowSelection}
|
||||||
pagination={TABLE_PAGINATION}
|
pagination={TABLE_PAGINATION}
|
||||||
|
expandable={{expandedRowRender: (record) => <div style={{ padding: 10 }}>{record.prompt}</div>}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
5
src/view/model/UserCustom/config.tsx
vendored
5
src/view/model/UserCustom/config.tsx
vendored
@ -1,4 +1,4 @@
|
|||||||
import { Tag, Switch, Tooltip, Space, Popconfirm } from 'antd';
|
import { Tag, Switch, Space, Popconfirm, Table } from 'antd';
|
||||||
|
|
||||||
export const modelColumns = () => [
|
export const modelColumns = () => [
|
||||||
{
|
{
|
||||||
@ -33,13 +33,14 @@ export const modelColumns = () => [
|
|||||||
<Switch checked={v} onChange={(v) => action.setRecord({ ...row, enable: v }, 'enable')} />
|
<Switch checked={v} onChange={(v) => action.setRecord({ ...row, enable: v }, 'enable')} />
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
Table.EXPAND_COLUMN,
|
||||||
{
|
{
|
||||||
title: 'Prompt',
|
title: 'Prompt',
|
||||||
dataIndex: 'prompt',
|
dataIndex: 'prompt',
|
||||||
key: 'prompt',
|
key: 'prompt',
|
||||||
width: 300,
|
width: 300,
|
||||||
render: (v: string) => (
|
render: (v: string) => (
|
||||||
<Tooltip overlayInnerStyle={{ width: 350 }} title={v}><span className="chat-prompts-val">{v}</span></Tooltip>
|
<span className="chat-prompts-val">{v}</span>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
5
src/view/model/UserCustom/index.tsx
vendored
5
src/view/model/UserCustom/index.tsx
vendored
@ -6,13 +6,13 @@ import useInit from '@/hooks/useInit';
|
|||||||
import useData from '@/hooks/useData';
|
import useData from '@/hooks/useData';
|
||||||
import useChatModel, { useCacheModel } from '@/hooks/useChatModel';
|
import useChatModel, { useCacheModel } from '@/hooks/useChatModel';
|
||||||
import useColumns from '@/hooks/useColumns';
|
import useColumns from '@/hooks/useColumns';
|
||||||
import useTable, { TABLE_PAGINATION } from '@/hooks/useTable';
|
import { useTableRowSelection, TABLE_PAGINATION } from '@/hooks/useTable';
|
||||||
import { chatRoot, fmtDate } from '@/utils';
|
import { chatRoot, fmtDate } from '@/utils';
|
||||||
import { modelColumns } from './config';
|
import { modelColumns } from './config';
|
||||||
import UserCustomForm from './Form';
|
import UserCustomForm from './Form';
|
||||||
|
|
||||||
export default function LanguageModel() {
|
export default function LanguageModel() {
|
||||||
const { rowSelection, selectedRowIDs } = useTable();
|
const { rowSelection, selectedRowIDs } = useTableRowSelection();
|
||||||
const [isVisible, setVisible] = useState(false);
|
const [isVisible, setVisible] = useState(false);
|
||||||
const [jsonPath, setJsonPath] = useState('');
|
const [jsonPath, setJsonPath] = useState('');
|
||||||
const { modelJson, modelSet } = useChatModel('user_custom');
|
const { modelJson, modelSet } = useChatModel('user_custom');
|
||||||
@ -123,6 +123,7 @@ export default function LanguageModel() {
|
|||||||
dataSource={opData}
|
dataSource={opData}
|
||||||
rowSelection={rowSelection}
|
rowSelection={rowSelection}
|
||||||
pagination={TABLE_PAGINATION}
|
pagination={TABLE_PAGINATION}
|
||||||
|
expandable={{expandedRowRender: (record) => <div style={{ padding: 10 }}>{record.prompt}</div>}}
|
||||||
/>
|
/>
|
||||||
<Modal
|
<Modal
|
||||||
open={isVisible}
|
open={isVisible}
|
||||||
|
69
src/view/notes/config.tsx
vendored
Normal file
69
src/view/notes/config.tsx
vendored
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import { Space, Popconfirm } from 'antd';
|
||||||
|
import { path, shell } from '@tauri-apps/api';
|
||||||
|
|
||||||
|
import { EditRow } from '@/hooks/useColumns';
|
||||||
|
|
||||||
|
import useInit from '@/hooks/useInit';
|
||||||
|
import { fmtDate, chatRoot } from '@/utils';
|
||||||
|
|
||||||
|
export const notesColumns = () => [
|
||||||
|
{
|
||||||
|
title: 'Name',
|
||||||
|
dataIndex: 'name',
|
||||||
|
fixed: 'left',
|
||||||
|
key: 'name',
|
||||||
|
width: 240,
|
||||||
|
render: (_: string, row: any, actions: any) => (
|
||||||
|
<EditRow rowKey="name" row={row} actions={actions} />
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Path',
|
||||||
|
dataIndex: 'path',
|
||||||
|
key: 'path',
|
||||||
|
width: 200,
|
||||||
|
render: (_: string, row: any) => <RenderPath row={row} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Created',
|
||||||
|
dataIndex: 'created',
|
||||||
|
key: 'created',
|
||||||
|
width: 150,
|
||||||
|
render: fmtDate,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Action',
|
||||||
|
fixed: 'right',
|
||||||
|
width: 160,
|
||||||
|
render: (_: any, row: any, actions: any) => {
|
||||||
|
return (
|
||||||
|
<Space>
|
||||||
|
<a onClick={() => actions.setRecord(row, 'preview')}>Preview</a>
|
||||||
|
<a onClick={() => actions.setRecord(row, 'edit')}>Edit</a>
|
||||||
|
<Popconfirm
|
||||||
|
title="Are you sure to delete this file?"
|
||||||
|
onConfirm={() => actions.setRecord(row, 'delete')}
|
||||||
|
okText="Yes"
|
||||||
|
cancelText="No"
|
||||||
|
>
|
||||||
|
<a>Delete</a>
|
||||||
|
</Popconfirm>
|
||||||
|
</Space>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const RenderPath = ({ row }: any) => {
|
||||||
|
const [filePath, setFilePath] = useState('');
|
||||||
|
useInit(async () => {
|
||||||
|
setFilePath(await getPath(row));
|
||||||
|
})
|
||||||
|
return <a onClick={() => shell.open(filePath)}>{filePath}</a>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getPath = async (row: any) => {
|
||||||
|
const isImg = ['png'].includes(row?.ext);
|
||||||
|
return await path.join(await chatRoot(), 'notes', row.id) + `.${row.ext}`;
|
||||||
|
}
|
160
src/view/notes/index.tsx
vendored
Normal file
160
src/view/notes/index.tsx
vendored
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { Table, Modal, Popconfirm, Button, message } from 'antd';
|
||||||
|
import { invoke, path, shell, fs } from '@tauri-apps/api';
|
||||||
|
import ReactMarkdown from 'react-markdown';
|
||||||
|
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
||||||
|
import { a11yDark } from 'react-syntax-highlighter/dist/esm/styles/prism';
|
||||||
|
|
||||||
|
import useInit from '@/hooks/useInit';
|
||||||
|
import useJson from '@/hooks/useJson';
|
||||||
|
import useData from '@/hooks/useData';
|
||||||
|
import useColumns from '@/hooks/useColumns';
|
||||||
|
import { useTableRowSelection, TABLE_PAGINATION } from '@/hooks/useTable';
|
||||||
|
import { chatRoot, CHAT_NOTES_JSON } from '@/utils';
|
||||||
|
import { notesColumns } from './config';
|
||||||
|
|
||||||
|
export default function Notes() {
|
||||||
|
const [notesPath, setNotesPath] = useState('');
|
||||||
|
const [source, setSource] = useState('');
|
||||||
|
const [isVisible, setVisible] = useState(false);
|
||||||
|
const { opData, opInit, opReplace, opSafeKey } = useData([]);
|
||||||
|
const { columns, ...opInfo } = useColumns(notesColumns());
|
||||||
|
const { rowSelection, selectedRows, rowReset } = useTableRowSelection({ rowType: 'row' });
|
||||||
|
const { json, refreshJson, updateJson } = useJson<any[]>(CHAT_NOTES_JSON);
|
||||||
|
const selectedItems = rowSelection.selectedRowKeys || [];
|
||||||
|
|
||||||
|
useInit(async () => {
|
||||||
|
const file = await path.join(await chatRoot(), CHAT_NOTES_JSON);
|
||||||
|
setNotesPath(file);
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!json || json.length <= 0) return;
|
||||||
|
opInit(json);
|
||||||
|
}, [json?.length]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!opInfo.opType) return;
|
||||||
|
(async () => {
|
||||||
|
const record = opInfo?.opRecord;
|
||||||
|
const file = await path.join(await chatRoot(), 'notes', `${record?.id}.${record?.ext}`);
|
||||||
|
if (opInfo.opType === 'preview') {
|
||||||
|
const data = await fs.readTextFile(file);
|
||||||
|
setSource(data);
|
||||||
|
setVisible(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (opInfo.opType === 'edit') {
|
||||||
|
alert('TODO');
|
||||||
|
}
|
||||||
|
if (opInfo.opType === 'delete') {
|
||||||
|
await fs.removeFile(file);
|
||||||
|
await handleRefresh();
|
||||||
|
}
|
||||||
|
if (opInfo.opType === 'rowedit') {
|
||||||
|
const data = opReplace(opInfo?.opRecord?.[opSafeKey], opInfo?.opRecord);
|
||||||
|
await updateJson(data);
|
||||||
|
message.success('Name has been changed!');
|
||||||
|
}
|
||||||
|
opInfo.resetRecord();
|
||||||
|
})()
|
||||||
|
}, [opInfo.opType])
|
||||||
|
|
||||||
|
const handleDelete = async () => {
|
||||||
|
if (opData?.length === selectedRows.length) {
|
||||||
|
const notesDir = await path.join(await chatRoot(), 'notes');
|
||||||
|
await fs.removeDir(notesDir, { recursive: true });
|
||||||
|
await handleRefresh();
|
||||||
|
rowReset();
|
||||||
|
message.success('All files have been cleared!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rows = selectedRows.map(async (i) => {
|
||||||
|
const file = await path.join(await chatRoot(), 'notes', `${i?.id}.${i?.ext}`);
|
||||||
|
await fs.removeFile(file);
|
||||||
|
return file;
|
||||||
|
})
|
||||||
|
Promise.all(rows).then(async () => {
|
||||||
|
await handleRefresh();
|
||||||
|
message.success('All files selected are cleared!');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRefresh = async () => {
|
||||||
|
await invoke('download_list', { pathname: CHAT_NOTES_JSON, dir: 'notes' });
|
||||||
|
const data = await refreshJson();
|
||||||
|
opInit(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
setVisible(false);
|
||||||
|
opInfo.resetRecord();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="chat-table-btns">
|
||||||
|
<div>
|
||||||
|
{selectedItems.length > 0 && (
|
||||||
|
<>
|
||||||
|
<Popconfirm
|
||||||
|
overlayStyle={{ width: 250 }}
|
||||||
|
title="Files cannot be recovered after deletion, are you sure you want to delete them?"
|
||||||
|
placement="topLeft"
|
||||||
|
onConfirm={handleDelete}
|
||||||
|
okText="Yes"
|
||||||
|
cancelText="No"
|
||||||
|
>
|
||||||
|
<Button>Batch delete</Button>
|
||||||
|
</Popconfirm>
|
||||||
|
<span className="num">Selected {selectedItems.length} items</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="chat-table-tip">
|
||||||
|
<div className="chat-file-path">
|
||||||
|
<div>PATH: <a onClick={() => shell.open(notesPath)} title={notesPath}>{notesPath}</a></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Table
|
||||||
|
rowKey="id"
|
||||||
|
columns={columns}
|
||||||
|
scroll={{ x: 800 }}
|
||||||
|
dataSource={opData}
|
||||||
|
rowSelection={rowSelection}
|
||||||
|
pagination={TABLE_PAGINATION}
|
||||||
|
/>
|
||||||
|
<Modal
|
||||||
|
open={isVisible}
|
||||||
|
title={<div>{opInfo?.opRecord?.name || ''}</div>}
|
||||||
|
onCancel={handleCancel}
|
||||||
|
footer={false}
|
||||||
|
destroyOnClose
|
||||||
|
>
|
||||||
|
<ReactMarkdown
|
||||||
|
children={source}
|
||||||
|
components={{
|
||||||
|
code({node, inline, className, children, ...props}) {
|
||||||
|
const match = /language-(\w+)/.exec(className || '')
|
||||||
|
return !inline && match ? (
|
||||||
|
<SyntaxHighlighter
|
||||||
|
children={String(children).replace(/\n$/, '')}
|
||||||
|
style={a11yDark as any}
|
||||||
|
language={match[1]}
|
||||||
|
PreTag="div"
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<code className={className} {...props}>
|
||||||
|
{children}
|
||||||
|
</code>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user