mirror of
https://github.com/lencx/ChatGPT.git
synced 2024-10-01 01:06:13 -04:00
chore: scripts
This commit is contained in:
parent
882593479b
commit
ee0829d8db
@ -5,7 +5,7 @@
|
||||
</p>
|
||||
|
||||
[![English badge](https://img.shields.io/badge/%E8%8B%B1%E6%96%87-English-blue)](./README.md)
|
||||
[![简体中文 badge](https://img.shields.io/badge/%E7%AE%80%E4%BD%93%E4%B8%AD%E6%96%87-Simplified%20Chinese-blue)](./README-ZH_CN.md)
|
||||
[![简体中文 badge](https://img.shields.io/badge/%E7%AE%80%E4%BD%93%E4%B8%AD%E6%96%87-Simplified%20Chinese-blue)](./README-ZH_CN.md)\
|
||||
[![ChatGPT downloads](https://img.shields.io/github/downloads/lencx/ChatGPT/total.svg?style=flat-square)](https://github.com/lencx/ChatGPT/releases)
|
||||
[![chat](https://img.shields.io/badge/chat-discord-blue?style=flat&logo=discord)](https://discord.gg/aPhCRf4zZr)
|
||||
[![lencx](https://img.shields.io/badge/follow-lencx__-blue?style=flat&logo=Twitter)](https://twitter.com/lencx_)
|
||||
|
1417
pnpm-lock.yaml
1417
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
@ -1,3 +1,5 @@
|
||||
# ChatGPT Scripts
|
||||
|
||||
ChatGPT Desktop Application Core Extension Scripts.
|
||||
> ChatGPT Desktop Application Core Extension Scripts.
|
||||
|
||||
[ChatGPT/scripts](https://github.com/lencx/ChatGPT/tree/main/scripts)
|
||||
|
10
scripts/chat.js
vendored
10
scripts/chat.js
vendored
@ -4,7 +4,7 @@
|
||||
* @url https://github.com/lencx/ChatGPT/tree/main/scripts/chat.js
|
||||
*/
|
||||
|
||||
var chatInit = (() => {
|
||||
function chatInit() {
|
||||
const ICONS = {
|
||||
copy: `<svg class="chatappico copy" stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="h-4 w-4" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"></path><rect x="8" y="2" width="8" height="4" rx="1" ry="1"></rect></svg>`,
|
||||
cpok: `<svg class="chatappico cpok" viewBox="0 0 24 24"><g fill="none" stroke="#10a37f" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><rect width="8" height="4" x="8" y="2" rx="1" ry="1"/><path d="M8 4H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-2M16 4h2a2 2 0 0 1 2 2v4m1 4H11"/><path d="m15 10l-4 4l4 4"/></g></svg>`,
|
||||
@ -114,12 +114,10 @@ var chatInit = (() => {
|
||||
currentIndex = -1;
|
||||
};
|
||||
}
|
||||
|
||||
return { init };
|
||||
})();
|
||||
}
|
||||
|
||||
if (document.readyState === 'complete' || document.readyState === 'interactive') {
|
||||
chatInit.init();
|
||||
chatInit();
|
||||
} else {
|
||||
document.addEventListener('DOMContentLoaded', chatInit.init);
|
||||
document.addEventListener('DOMContentLoaded', chatInit);
|
||||
}
|
||||
|
14
scripts/cmd.js
vendored
14
scripts/cmd.js
vendored
@ -4,7 +4,7 @@
|
||||
* @url https://github.com/lencx/ChatGPT/tree/main/scripts/cmd.js
|
||||
*/
|
||||
|
||||
function init() {
|
||||
function cmdInit() {
|
||||
const styleDom = document.createElement('style');
|
||||
styleDom.innerHTML = `form {
|
||||
position: relative;
|
||||
@ -140,9 +140,8 @@ function init() {
|
||||
subtree: true,
|
||||
});
|
||||
}, 300);
|
||||
}
|
||||
|
||||
async function cmdTip() {
|
||||
async function cmdTip() {
|
||||
initDom();
|
||||
const chatPromptJson = (await invoke('get_chat_prompt_cmd')) || {};
|
||||
const data = chatPromptJson.data;
|
||||
@ -321,9 +320,9 @@ async function cmdTip() {
|
||||
searchInput.removeEventListener('input', cmdInput);
|
||||
searchInput.addEventListener('input', cmdInput);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function initDom() {
|
||||
function initDom() {
|
||||
const promptDom = document.querySelector('.chat-prompt-cmd-list');
|
||||
if (promptDom) {
|
||||
promptDom.innerHTML = '';
|
||||
@ -333,10 +332,11 @@ function initDom() {
|
||||
delete window.__CHAT_STATUS__;
|
||||
delete window.__cmd_list;
|
||||
delete window.__cmd_index;
|
||||
}
|
||||
}
|
||||
|
||||
if (document.readyState === 'complete' || document.readyState === 'interactive') {
|
||||
init();
|
||||
cmdInit();
|
||||
} else {
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
document.addEventListener('DOMContentLoaded', cmdInit);
|
||||
}
|
||||
|
11
scripts/core.js
vendored
11
scripts/core.js
vendored
@ -4,7 +4,7 @@
|
||||
* @url https://github.com/lencx/ChatGPT/tree/main/scripts/core.js
|
||||
*/
|
||||
|
||||
var coreInit = (() => {
|
||||
function coreInit() {
|
||||
const uid = () => window.crypto.getRandomValues(new Uint32Array(1))[0];
|
||||
function transformCallback(callback = () => {}, once = false) {
|
||||
const identifier = uid();
|
||||
@ -21,6 +21,7 @@ var coreInit = (() => {
|
||||
});
|
||||
return identifier;
|
||||
}
|
||||
|
||||
async function invoke(cmd, args) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!window.__TAURI_POST_MESSAGE__) reject('__TAURI_POST_MESSAGE__ does not exist!');
|
||||
@ -226,11 +227,11 @@ var coreInit = (() => {
|
||||
return { startLoading, stopLoading };
|
||||
}
|
||||
|
||||
return { init };
|
||||
})();
|
||||
init();
|
||||
}
|
||||
|
||||
if (document.readyState === 'complete' || document.readyState === 'interactive') {
|
||||
coreInit.init();
|
||||
coreInit();
|
||||
} else {
|
||||
document.addEventListener('DOMContentLoaded', coreInit.init);
|
||||
document.addEventListener('DOMContentLoaded', coreInit);
|
||||
}
|
||||
|
6
scripts/dalle2.js
vendored
6
scripts/dalle2.js
vendored
@ -4,7 +4,7 @@
|
||||
* @url https://github.com/lencx/ChatGPT/tree/main/scripts/dalle2.js
|
||||
*/
|
||||
|
||||
function init() {
|
||||
function dalle2Init() {
|
||||
document.addEventListener('click', (e) => {
|
||||
const origin = e.target.closest('a');
|
||||
if (!origin || !origin.target) return;
|
||||
@ -35,7 +35,7 @@ function init() {
|
||||
}
|
||||
|
||||
if (document.readyState === 'complete' || document.readyState === 'interactive') {
|
||||
init();
|
||||
dalle2Init();
|
||||
} else {
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
document.addEventListener('DOMContentLoaded', dalle2Init);
|
||||
}
|
||||
|
10
scripts/export.js
vendored
10
scripts/export.js
vendored
@ -4,7 +4,7 @@
|
||||
* @url https://github.com/lencx/ChatGPT/tree/main/scripts/export.js
|
||||
*/
|
||||
|
||||
async function init() {
|
||||
async function exportInit() {
|
||||
if (window.location.pathname === '/auth/login') return;
|
||||
const buttonOuterHTMLFallback = `<button class="btn flex justify-center gap-2 btn-neutral">Try Again</button>`;
|
||||
removeButtons();
|
||||
@ -303,7 +303,7 @@ async function init() {
|
||||
const hours = String(now.getHours()).padStart(2, '0');
|
||||
const minutes = String(now.getMinutes()).padStart(2, '0');
|
||||
const seconds = String(now.getSeconds()).padStart(2, '0');
|
||||
const formattedDateTime = `${year}_${month}_${day}_${hours}_${minutes}_${seconds}`;
|
||||
const formattedDateTime = `${year}_${month}_${day}-${hours}${minutes}${seconds}`;
|
||||
return formattedDateTime;
|
||||
}
|
||||
|
||||
@ -315,10 +315,10 @@ async function init() {
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('resize', init);
|
||||
window.addEventListener('resize', exportInit);
|
||||
|
||||
if (document.readyState === 'complete' || document.readyState === 'interactive') {
|
||||
init();
|
||||
exportInit();
|
||||
} else {
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
document.addEventListener('DOMContentLoaded', exportInit);
|
||||
}
|
||||
|
5
scripts/main.js
vendored
Normal file
5
scripts/main.js
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
// *** ChatGPT User Script ***
|
||||
// @github: https://github.com/lencx/ChatGPT
|
||||
// @path: $HOME/.chatgpt/scripts/main.js
|
||||
|
||||
console.log('Hello, ChatGPT!');
|
@ -12,6 +12,10 @@
|
||||
"name": "cmd.js",
|
||||
"version": "0.1.0"
|
||||
},
|
||||
{
|
||||
"name": "chat.js",
|
||||
"version": "0.1.0"
|
||||
},
|
||||
{
|
||||
"name": "core.js",
|
||||
"version": "0.1.0"
|
||||
|
6
scripts/popup.core.js
vendored
6
scripts/popup.core.js
vendored
@ -4,7 +4,7 @@
|
||||
* @url https://github.com/lencx/ChatGPT/tree/main/scripts/popup.core.js
|
||||
*/
|
||||
|
||||
async function init() {
|
||||
async function popupCoreInit() {
|
||||
const chatConf = (await invoke('get_app_conf')) || {};
|
||||
if (!chatConf.popup_search) return;
|
||||
if (!window.FloatingUIDOM) return;
|
||||
@ -77,7 +77,7 @@ async function init() {
|
||||
}
|
||||
|
||||
if (document.readyState === 'complete' || document.readyState === 'interactive') {
|
||||
init();
|
||||
popupCoreInit();
|
||||
} else {
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
document.addEventListener('DOMContentLoaded', popupCoreInit);
|
||||
}
|
||||
|
@ -9,8 +9,6 @@ use std::{collections::HashMap, fs, path::PathBuf, vec};
|
||||
use tauri::{api, command, AppHandle, Manager};
|
||||
use walkdir::WalkDir;
|
||||
|
||||
use super::fs_extra::Error;
|
||||
|
||||
#[command]
|
||||
pub fn get_chat_prompt_cmd() -> serde_json::Value {
|
||||
let path = utils::app_root().join("chat.prompt.cmd.json");
|
||||
@ -183,13 +181,11 @@ pub async fn sync_prompts(app: AppHandle, time: u64) -> Option<Vec<PromptRecord>
|
||||
},
|
||||
act: i.act.clone(),
|
||||
prompt: i.prompt.clone(),
|
||||
tags: vec!["chatgpt-prompts".to_string()],
|
||||
tags: vec!["awesome-chatgpt-prompts".to_string()],
|
||||
enable: true,
|
||||
})
|
||||
.collect::<Vec<PromptRecord>>();
|
||||
|
||||
let data2 = transformed_data;
|
||||
|
||||
let prompts = utils::app_root().join("chat.prompt.json");
|
||||
let prompt_cmd = utils::app_root().join("chat.prompt.cmd.json");
|
||||
let chatgpt_prompts = utils::app_root()
|
||||
@ -211,7 +207,7 @@ pub async fn sync_prompts(app: AppHandle, time: u64) -> Option<Vec<PromptRecord>
|
||||
// chatgpt_prompts.json
|
||||
fs::write(
|
||||
chatgpt_prompts,
|
||||
serde_json::to_string_pretty(&data).unwrap(),
|
||||
serde_json::to_string_pretty(&transformed_data).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
let cmd_data = cmd_list();
|
||||
@ -253,7 +249,7 @@ pub async fn sync_prompts(app: AppHandle, time: u64) -> Option<Vec<PromptRecord>
|
||||
window::cmd::window_reload(app.clone(), "core");
|
||||
window::cmd::window_reload(app, "tray");
|
||||
|
||||
return Some(data2);
|
||||
return Some(transformed_data);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -93,9 +93,6 @@ pub fn init() -> Menu {
|
||||
#[cfg(target_os = "macos")]
|
||||
hide_dock_icon_menu.into(),
|
||||
system_tray_menu.into(),
|
||||
CustomMenuItem::new("inject_script", "Inject Script")
|
||||
.accelerator("CmdOrCtrl+J")
|
||||
.into(),
|
||||
MenuItem::Separator.into(),
|
||||
Submenu::new(
|
||||
"Theme",
|
||||
@ -219,7 +216,6 @@ pub fn init() -> Menu {
|
||||
pub fn menu_handler(event: WindowMenuEvent<tauri::Wry>) {
|
||||
let win = Some(event.window()).unwrap();
|
||||
let app = win.app_handle();
|
||||
let script_path = utils::script_path().to_string_lossy().to_string();
|
||||
let menu_id = event.menu_item_id();
|
||||
let menu_handle = win.menu_handle();
|
||||
|
||||
@ -239,7 +235,6 @@ pub fn menu_handler(event: WindowMenuEvent<tauri::Wry>) {
|
||||
// Preferences
|
||||
"control_center" => window::cmd::control_window(app, "control".into()),
|
||||
"restart" => tauri::api::process::restart(&app.env()),
|
||||
"inject_script" => open(&app, &script_path),
|
||||
"go_conf" => utils::open_file(utils::app_root()),
|
||||
"clear_conf" => utils::clear_conf(&app),
|
||||
"app_website" => window::cmd::wa_window(
|
||||
|
@ -4,4 +4,5 @@ pub mod gpt;
|
||||
pub mod menu;
|
||||
pub mod script;
|
||||
pub mod setup;
|
||||
pub mod template;
|
||||
pub mod window;
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::utils::{app_root, exists};
|
||||
use crate::utils::{app_root, create_file, exists};
|
||||
use log::error;
|
||||
use log::info;
|
||||
use regex::Regex;
|
||||
@ -6,10 +6,13 @@ use serde_json::{from_str, json, Value};
|
||||
use std::fs;
|
||||
use tauri::Manager;
|
||||
|
||||
use crate::{conf::SCRIPTS_MANIFEST, window};
|
||||
use crate::{conf::SCRIPTS_DIR, window};
|
||||
|
||||
pub async fn init_script(app: tauri::AppHandle) -> anyhow::Result<(), reqwest::Error> {
|
||||
let body = reqwest::get(SCRIPTS_MANIFEST).await?.text().await?;
|
||||
let body = reqwest::get(format!("{}{}", SCRIPTS_DIR, "manifest.json"))
|
||||
.await?
|
||||
.text()
|
||||
.await?;
|
||||
|
||||
if exist_scripts("manifest.json".into()) {
|
||||
let compare = compare_nested_json_objects(
|
||||
@ -39,8 +42,16 @@ pub async fn init_script(app: tauri::AppHandle) -> anyhow::Result<(), reqwest::E
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn parse_script(name: String) -> serde_json::Value {
|
||||
let code = &fs::read_to_string(name).unwrap();
|
||||
pub fn parse_script(name: String) -> Option<serde_json::Value> {
|
||||
let script_file = app_root().join("scripts").join(name);
|
||||
let code = match fs::read_to_string(&script_file) {
|
||||
Ok(content) => content,
|
||||
Err(_) => {
|
||||
error!("parse_script_error: {}", script_file.display());
|
||||
return None;
|
||||
}
|
||||
};
|
||||
// let code = &fs::read_to_string(name).unwrap();
|
||||
let re_name = Regex::new(r"@name\s+(.*?)\n").unwrap();
|
||||
let re_version = Regex::new(r"@version\s+(.*?)\n").unwrap();
|
||||
let re_url = Regex::new(r"@url\s+(.*?)\n").unwrap();
|
||||
@ -49,15 +60,15 @@ pub fn parse_script(name: String) -> serde_json::Value {
|
||||
let mut version = String::new();
|
||||
let mut url = String::new();
|
||||
|
||||
if let Some(capture) = re_name.captures(code) {
|
||||
if let Some(capture) = re_name.captures(&code) {
|
||||
name = capture.get(1).unwrap().as_str().trim().to_owned();
|
||||
}
|
||||
|
||||
if let Some(capture) = re_version.captures(code) {
|
||||
if let Some(capture) = re_version.captures(&code) {
|
||||
version = capture.get(1).unwrap().as_str().trim().to_owned();
|
||||
}
|
||||
|
||||
if let Some(capture) = re_url.captures(code) {
|
||||
if let Some(capture) = re_url.captures(&code) {
|
||||
url = capture.get(1).unwrap().as_str().trim().to_owned();
|
||||
}
|
||||
|
||||
@ -67,7 +78,7 @@ pub fn parse_script(name: String) -> serde_json::Value {
|
||||
"url": url,
|
||||
});
|
||||
|
||||
json_data
|
||||
Some(json_data)
|
||||
}
|
||||
|
||||
pub fn exist_scripts(file: String) -> bool {
|
||||
@ -77,8 +88,15 @@ pub fn exist_scripts(file: String) -> bool {
|
||||
|
||||
pub fn create_chatgpt_scripts(file: String, body: String) {
|
||||
let script_file = app_root().join("scripts").join(file);
|
||||
match create_file(&script_file) {
|
||||
Ok(_) => {
|
||||
info!("script_file: {:?}", script_file);
|
||||
fs::write(&script_file, body).unwrap();
|
||||
}
|
||||
Err(e) => {
|
||||
error!("create_file, {}: {}", script_file.display(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn compare_nested_json_objects(json1: &str, json2: &str) -> bool {
|
||||
@ -121,3 +139,53 @@ pub fn compare_json_objects(obj1: &Value, obj2: &Value) -> bool {
|
||||
_ => obj1 == obj2,
|
||||
}
|
||||
}
|
||||
|
||||
pub mod cmd {
|
||||
use super::{create_chatgpt_scripts, parse_script};
|
||||
use crate::conf::SCRIPTS_DIR;
|
||||
use log::{error, info};
|
||||
use tauri::Manager;
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_script_info(name: String) -> Option<serde_json::Value> {
|
||||
parse_script(name)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn sync_scripts(app: tauri::AppHandle, name: String) -> bool {
|
||||
let res = reqwest::get(format!("{}{}", SCRIPTS_DIR, name)).await;
|
||||
|
||||
info!("sync_scripts: {}", name);
|
||||
let body = match res {
|
||||
Ok(response) => match response.text().await {
|
||||
Ok(text) => text,
|
||||
Err(err) => {
|
||||
error!("sync_scripts_result_error: {}", err);
|
||||
if let Some(v) = app.get_window("core") {
|
||||
tauri::api::dialog::message(
|
||||
Some(&v),
|
||||
"Sync Scripts Error",
|
||||
format!("sync_scripts_result_error: {}", err),
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
error!("sync_scripts_response_error: {}", err);
|
||||
if let Some(v) = app.get_window("core") {
|
||||
tauri::api::dialog::message(
|
||||
Some(&v),
|
||||
"Sync Scripts Error",
|
||||
format!("sync_scripts_response_error: {}", err),
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
create_chatgpt_scripts(name, body);
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,8 @@
|
||||
use crate::{app, conf::AppConf, utils};
|
||||
use crate::{
|
||||
app,
|
||||
conf::AppConf,
|
||||
utils::{self, load_script},
|
||||
};
|
||||
use log::{error, info};
|
||||
use tauri::{utils::config::WindowUrl, window::WindowBuilder, App, GlobalShortcutManager, Manager};
|
||||
use wry::application::accelerator::Accelerator;
|
||||
@ -10,6 +14,8 @@ pub fn init(app: &mut App) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let url = app_conf.main_origin.to_string();
|
||||
let theme = AppConf::theme_mode();
|
||||
|
||||
app::template::Template::new(utils::app_root().join("scripts"));
|
||||
|
||||
let handle = app.app_handle();
|
||||
tauri::async_runtime::spawn(async move {
|
||||
app::script::init_script(handle)
|
||||
@ -71,7 +77,7 @@ pub fn init(app: &mut App) -> Result<(), Box<dyn std::error::Error>> {
|
||||
.theme(Some(theme))
|
||||
.always_on_top(app_conf2.stay_on_top)
|
||||
.initialization_script(&utils::user_script())
|
||||
.initialization_script(include_str!("../../../scripts/core.js"))
|
||||
.initialization_script(&load_script("core.js"))
|
||||
.user_agent(&app_conf2.ua_window);
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
@ -89,11 +95,11 @@ pub fn init(app: &mut App) -> Result<(), Box<dyn std::error::Error>> {
|
||||
.initialization_script(include_str!("../vendors/jspdf.js"))
|
||||
.initialization_script(include_str!("../vendors/turndown.js"))
|
||||
.initialization_script(include_str!("../vendors/turndown-plugin-gfm.js"))
|
||||
.initialization_script(include_str!("../../../scripts/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"))
|
||||
.initialization_script(include_str!("../../../scripts/chat.js"))
|
||||
.initialization_script(&load_script("popup.core.js"))
|
||||
.initialization_script(&load_script("export.js"))
|
||||
.initialization_script(&load_script("markdown.export.js"))
|
||||
.initialization_script(&load_script("cmd.js"))
|
||||
.initialization_script(&load_script("chat.js"))
|
||||
}
|
||||
|
||||
main_win.build().unwrap();
|
||||
|
110
src-tauri/src/app/template.rs
Normal file
110
src-tauri/src/app/template.rs
Normal file
@ -0,0 +1,110 @@
|
||||
use anyhow::Result;
|
||||
use log::{error, info};
|
||||
use std::{
|
||||
fs::{self, File},
|
||||
io::Write,
|
||||
path::Path,
|
||||
};
|
||||
|
||||
pub static SCRIPT_MAIN: &[u8] = include_bytes!("../../../scripts/main.js");
|
||||
pub static SCRIPT_CORE: &[u8] = include_bytes!("../../../scripts/core.js");
|
||||
pub static SCRIPT_CHAT: &[u8] = include_bytes!("../../../scripts/chat.js");
|
||||
pub static SCRIPT_CMD: &[u8] = include_bytes!("../../../scripts/cmd.js");
|
||||
pub static SCRIPT_DALLE2: &[u8] = include_bytes!("../../../scripts/dalle2.js");
|
||||
pub static SCRIPT_EXPORT: &[u8] = include_bytes!("../../../scripts/export.js");
|
||||
pub static SCRIPT_MD_EXPORT: &[u8] = include_bytes!("../../../scripts/markdown.export.js");
|
||||
pub static SCRIPT_POPUP_CORE: &[u8] = include_bytes!("../../../scripts/popup.core.js");
|
||||
pub static SCRIPT_MANIFEST: &[u8] = include_bytes!("../../../scripts/manifest.json");
|
||||
pub static SCRIPT_README: &[u8] = include_bytes!("../../../scripts/README.md");
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Template {
|
||||
pub main: Vec<u8>,
|
||||
pub core: Vec<u8>,
|
||||
pub chat: Vec<u8>,
|
||||
pub cmd: Vec<u8>,
|
||||
pub dalle2: Vec<u8>,
|
||||
pub export: Vec<u8>,
|
||||
pub markdown_export: Vec<u8>,
|
||||
pub popup_core: Vec<u8>,
|
||||
pub manifest: Vec<u8>,
|
||||
pub readme: Vec<u8>,
|
||||
}
|
||||
|
||||
impl Template {
|
||||
pub fn new<P: AsRef<Path>>(template_dir: P) -> Self {
|
||||
let template_dir = template_dir.as_ref();
|
||||
let mut template = Template::default();
|
||||
|
||||
{
|
||||
let files = vec![
|
||||
(template_dir.join("main.js"), &mut template.main),
|
||||
(template_dir.join("core.js"), &mut template.core),
|
||||
(template_dir.join("chat.js"), &mut template.chat),
|
||||
(template_dir.join("cmd.js"), &mut template.cmd),
|
||||
(template_dir.join("dalle2.js"), &mut template.dalle2),
|
||||
(template_dir.join("export.js"), &mut template.export),
|
||||
(
|
||||
template_dir.join("markdown.export.js"),
|
||||
&mut template.markdown_export,
|
||||
),
|
||||
(template_dir.join("popup.core.js"), &mut template.popup_core),
|
||||
(template_dir.join("README.md"), &mut template.readme),
|
||||
(template_dir.join("manifest.json"), &mut template.manifest),
|
||||
];
|
||||
|
||||
for (filename, dest) in files {
|
||||
if !filename.exists() {
|
||||
match create_dir(&filename) {
|
||||
Ok(_) => {
|
||||
if let Err(e) = write_file_contents(&filename, dest) {
|
||||
error!("write_script, {}: {}", filename.display(), e);
|
||||
} else {
|
||||
info!("write_script: {}", filename.display());
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!("create_file, {}: {}", filename.display(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Template {
|
||||
fn default() -> Template {
|
||||
Template {
|
||||
main: Vec::from(SCRIPT_MAIN),
|
||||
core: Vec::from(SCRIPT_CORE),
|
||||
chat: Vec::from(SCRIPT_CHAT),
|
||||
cmd: Vec::from(SCRIPT_CMD),
|
||||
dalle2: Vec::from(SCRIPT_DALLE2),
|
||||
export: Vec::from(SCRIPT_EXPORT),
|
||||
markdown_export: Vec::from(SCRIPT_MD_EXPORT),
|
||||
popup_core: Vec::from(SCRIPT_POPUP_CORE),
|
||||
manifest: Vec::from(SCRIPT_MANIFEST),
|
||||
readme: Vec::from(SCRIPT_README),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn create_dir<P: AsRef<Path>>(filename: P) -> Result<()> {
|
||||
let filename = filename.as_ref();
|
||||
if let Some(parent) = filename.parent() {
|
||||
if !parent.exists() {
|
||||
fs::create_dir_all(parent)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_file_contents<P: AsRef<Path>>(filename: P, data: &[u8]) -> Result<()> {
|
||||
let filename = filename.as_ref();
|
||||
let mut file = File::create(filename)?;
|
||||
file.write_all(data)?;
|
||||
Ok(())
|
||||
}
|
@ -1,4 +1,7 @@
|
||||
use crate::{conf::AppConf, utils};
|
||||
use crate::{
|
||||
conf::AppConf,
|
||||
utils::{self, load_script},
|
||||
};
|
||||
use log::info;
|
||||
use std::time::SystemTime;
|
||||
use tauri::{utils::config::WindowUrl, window::WindowBuilder, Manager};
|
||||
@ -23,16 +26,16 @@ pub fn tray_window(handle: &tauri::AppHandle) {
|
||||
.always_on_top(true)
|
||||
.theme(Some(theme))
|
||||
.initialization_script(&utils::user_script())
|
||||
.initialization_script(include_str!("../../../scripts/core.js"))
|
||||
.initialization_script(&load_script("core.js"))
|
||||
.user_agent(&app_conf.ua_tray);
|
||||
|
||||
if app_conf.tray_origin == "https://chat.openai.com" && !app_conf.tray_dashboard {
|
||||
tray_win = tray_win
|
||||
.initialization_script(include_str!("../vendors/floating-ui-core.js"))
|
||||
.initialization_script(include_str!("../vendors/floating-ui-dom.js"))
|
||||
.initialization_script(include_str!("../../../scripts/cmd.js"))
|
||||
.initialization_script(include_str!("../../../scripts/chat.js"))
|
||||
.initialization_script(include_str!("../../../scripts/popup.core.js"))
|
||||
.initialization_script(&load_script("cmd.js"))
|
||||
.initialization_script(&load_script("chat.js"))
|
||||
.initialization_script(&load_script("popup.core.js"))
|
||||
}
|
||||
|
||||
tray_win.build().unwrap().hide().unwrap();
|
||||
@ -78,9 +81,9 @@ pub fn dalle2_window(
|
||||
.inner_size(800.0, 600.0)
|
||||
.always_on_top(false)
|
||||
.theme(Some(theme))
|
||||
.initialization_script(include_str!("../../../scripts/core.js"))
|
||||
.initialization_script(&load_script("core.js"))
|
||||
.initialization_script(&load_script("dalle2.js"))
|
||||
.initialization_script(&query)
|
||||
.initialization_script(include_str!("../../../scripts/dalle2.js"))
|
||||
.build()
|
||||
.unwrap();
|
||||
});
|
||||
@ -161,7 +164,7 @@ pub mod cmd {
|
||||
tauri::async_runtime::spawn(async move {
|
||||
tauri::WindowBuilder::new(&app, label, tauri::WindowUrl::App(url.parse().unwrap()))
|
||||
.initialization_script(&script.unwrap_or_default())
|
||||
.initialization_script(include_str!("../../../scripts/core.js"))
|
||||
.initialization_script(&load_script("core.js"))
|
||||
.title(title)
|
||||
.inner_size(960.0, 700.0)
|
||||
.resizable(true)
|
||||
|
@ -15,8 +15,7 @@ pub const UPDATE_LOG_URL: &str = "https://github.com/lencx/ChatGPT/blob/main/UPD
|
||||
// pub const BUY_COFFEE: &str = "https://www.buymeacoffee.com/lencx";
|
||||
pub const GITHUB_PROMPTS_CSV_URL: &str =
|
||||
"https://raw.githubusercontent.com/f/awesome-chatgpt-prompts/main/prompts.csv";
|
||||
pub const SCRIPTS_MANIFEST: &str =
|
||||
"https://raw.githubusercontent.com/lencx/ChatGPT/main/scripts/manifest.json";
|
||||
pub const SCRIPTS_DIR: &str = "https://raw.githubusercontent.com/lencx/ChatGPT/main/scripts/";
|
||||
|
||||
pub const APP_CONF_PATH: &str = "chat.conf.json";
|
||||
pub const CHATGPT_URL: &str = "https://chat.openai.com";
|
||||
|
@ -7,7 +7,7 @@ mod app;
|
||||
mod conf;
|
||||
mod utils;
|
||||
|
||||
use app::{cmd, fs_extra, gpt, menu, setup, window};
|
||||
use app::{cmd, fs_extra, gpt, menu, script, setup, window};
|
||||
use conf::AppConf;
|
||||
use tauri_plugin_autostart::MacosLauncher;
|
||||
use tauri_plugin_log::{
|
||||
@ -76,6 +76,8 @@ async fn main() {
|
||||
conf::cmd::form_confirm,
|
||||
conf::cmd::form_cancel,
|
||||
conf::cmd::form_msg,
|
||||
script::cmd::sync_scripts,
|
||||
script::cmd::get_script_info,
|
||||
window::cmd::wa_window,
|
||||
window::cmd::control_window,
|
||||
window::cmd::window_reload,
|
||||
|
@ -4,7 +4,7 @@ use regex::Regex;
|
||||
use serde_json::Value;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fs::{self, File},
|
||||
fs,
|
||||
path::{Path, PathBuf},
|
||||
process::Command,
|
||||
};
|
||||
@ -25,11 +25,15 @@ pub fn exists(path: &Path) -> bool {
|
||||
Path::new(path).exists()
|
||||
}
|
||||
|
||||
pub fn create_file(path: &Path) -> Result<File> {
|
||||
if let Some(p) = path.parent() {
|
||||
fs::create_dir_all(p)?
|
||||
pub fn create_file<P: AsRef<Path>>(filename: P) -> Result<()> {
|
||||
let filename = filename.as_ref();
|
||||
if let Some(parent) = filename.parent() {
|
||||
if !parent.exists() {
|
||||
fs::create_dir_all(parent)?;
|
||||
}
|
||||
File::create(path).map_err(Into::into)
|
||||
}
|
||||
fs::File::create(filename)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn create_chatgpt_prompts() {
|
||||
@ -42,31 +46,20 @@ pub fn create_chatgpt_prompts() {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn script_path() -> PathBuf {
|
||||
let script_file = app_root().join("main.js");
|
||||
if !exists(&script_file) {
|
||||
create_file(&script_file).unwrap();
|
||||
fs::write(
|
||||
&script_file,
|
||||
format!(
|
||||
"// *** ChatGPT User Script ***\n// @github: https://github.com/lencx/ChatGPT \n// @path: {}\n\nconsole.log('🤩 Hello ChatGPT!!!');",
|
||||
&script_file.to_string_lossy()
|
||||
),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
script_file
|
||||
}
|
||||
|
||||
pub fn user_script() -> String {
|
||||
let user_script_content = fs::read_to_string(script_path()).unwrap_or_else(|_| "".to_string());
|
||||
let user_script_file = app_root().join("scripts").join("main.js");
|
||||
let user_script_content = fs::read_to_string(user_script_file).unwrap_or_else(|_| "".to_string());
|
||||
format!(
|
||||
"window.addEventListener('DOMContentLoaded', function() {{\n{}\n}})",
|
||||
user_script_content
|
||||
)
|
||||
}
|
||||
|
||||
pub fn load_script(filename: &str) -> String {
|
||||
let script_file = app_root().join("scripts").join(filename);
|
||||
fs::read_to_string(script_file).unwrap_or_else(|_| "".to_string())
|
||||
}
|
||||
|
||||
pub fn open_file(path: PathBuf) {
|
||||
let pathname = convert_path(path.to_str().unwrap());
|
||||
info!("open_file: {}", pathname);
|
||||
|
3
src/components/FilePath/index.tsx
vendored
3
src/components/FilePath/index.tsx
vendored
@ -16,7 +16,8 @@ const FilePath: FC<FilePathProps> = ({ className, label = 'PATH', paths = '', ur
|
||||
const [filePath, setPath] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
if (!path && !url) return;
|
||||
if (!(paths || url)) return;
|
||||
|
||||
(async () => {
|
||||
if (url) {
|
||||
setPath(url);
|
||||
|
1
src/hooks/useColumns.tsx
vendored
1
src/hooks/useColumns.tsx
vendored
@ -74,6 +74,7 @@ export const EditRow: FC<EditRowProps> = ({ rowKey, row, actions }) => {
|
||||
onChange={handleChange}
|
||||
{...DISABLE_AUTO_COMPLETE}
|
||||
onPressEnter={handleSave}
|
||||
onBlur={handleSave}
|
||||
/>
|
||||
) : (
|
||||
<div className="rowedit" onClick={handleEdit}>
|
||||
|
22
src/main.scss
vendored
22
src/main.scss
vendored
@ -114,3 +114,25 @@ body,
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.editor-task {
|
||||
margin-bottom: 5px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.ant-breadcrumb-link {
|
||||
padding: 3px 5px;
|
||||
transition: all 300ms ease;
|
||||
border-radius: 4px;
|
||||
&:hover {
|
||||
color: rgba(0, 0, 0, 0.88);
|
||||
background-color: rgba(0, 0, 0, 0.06);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.editor-btn {
|
||||
cursor: pointer;
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
|
6
src/routes.tsx
vendored
6
src/routes.tsx
vendored
@ -14,6 +14,7 @@ import type { MenuProps } from 'antd';
|
||||
import Settings from '@/view/settings';
|
||||
import About from '@/view/about';
|
||||
import Scripts from '@/view/scripts';
|
||||
import ScriptsEditor from '@/view/scripts/Editor';
|
||||
import UserCustom from '@/view/prompts/UserCustom';
|
||||
import SyncPrompts from '@/view/prompts/SyncPrompts';
|
||||
import SyncCustom from '@/view/prompts/SyncCustom';
|
||||
@ -103,6 +104,11 @@ export const routes: Array<ChatRouteObject> = [
|
||||
icon: <CodeOutlined />,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/scripts/:id',
|
||||
element: <ScriptsEditor />,
|
||||
hideMenu: true,
|
||||
},
|
||||
{
|
||||
path: '/about',
|
||||
element: <About />,
|
||||
|
4
src/utils.ts
vendored
4
src/utils.ts
vendored
@ -23,6 +23,10 @@ export const chatRoot = async () => {
|
||||
return join(await homeDir(), '.chatgpt');
|
||||
};
|
||||
|
||||
export const scriptRoot = async () => {
|
||||
return join(await chatRoot(), 'scripts');
|
||||
};
|
||||
|
||||
export const chatPromptPath = async (): Promise<string> => {
|
||||
return join(await chatRoot(), CHAT_PROMPT_JSON);
|
||||
};
|
||||
|
16
src/view/markdown/index.scss
vendored
16
src/view/markdown/index.scss
vendored
@ -1,16 +0,0 @@
|
||||
.md-task {
|
||||
margin-bottom: 5px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.ant-breadcrumb-link {
|
||||
padding: 3px 5px;
|
||||
transition: all 300ms ease;
|
||||
border-radius: 4px;
|
||||
&:hover {
|
||||
color: rgba(0, 0, 0, 0.88);
|
||||
background-color: rgba(0, 0, 0, 0.06);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
3
src/view/markdown/index.tsx
vendored
3
src/view/markdown/index.tsx
vendored
@ -8,7 +8,6 @@ import { fs, shell } from '@tauri-apps/api';
|
||||
import useInit from '@/hooks/useInit';
|
||||
import SplitIcon from '@/icons/SplitIcon';
|
||||
import { getPath } from '@/view/notes/config';
|
||||
import './index.scss';
|
||||
|
||||
const modeMap: any = {
|
||||
0: 'split',
|
||||
@ -41,7 +40,7 @@ export default function Markdown() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="md-task">
|
||||
<div className="editor-task">
|
||||
<Breadcrumb separator="">
|
||||
<Breadcrumb.Item onClick={() => history.go(-1)}>
|
||||
<ArrowLeftOutlined />
|
||||
|
2
src/view/notes/config.tsx
vendored
2
src/view/notes/config.tsx
vendored
@ -67,5 +67,5 @@ const RenderPath = ({ row }: any) => {
|
||||
};
|
||||
|
||||
export const getPath = async (row: any) => {
|
||||
return (await path.join(await chatRoot(), 'notes', row.id)) + `.${row.ext}`;
|
||||
return await path.join(await chatRoot(), 'notes', `${row.id}.md`);
|
||||
};
|
||||
|
7
src/view/prompts/SyncPrompts/config.tsx
vendored
7
src/view/prompts/SyncPrompts/config.tsx
vendored
@ -7,7 +7,6 @@ export const syncColumns = () => [
|
||||
title: '/{cmd}',
|
||||
dataIndex: 'cmd',
|
||||
fixed: 'left',
|
||||
// width: 120,
|
||||
key: 'cmd',
|
||||
render: (_: string, row: Record<string, string>) => (
|
||||
<Tag color="#2a2a2a">/{genCmd(row.act)}</Tag>
|
||||
@ -17,20 +16,17 @@ export const syncColumns = () => [
|
||||
title: 'Act',
|
||||
dataIndex: 'act',
|
||||
key: 'act',
|
||||
// width: 200,
|
||||
},
|
||||
{
|
||||
title: 'Tags',
|
||||
dataIndex: 'tags',
|
||||
key: 'tags',
|
||||
// width: 150,
|
||||
render: () => <Tag>chatgpt-prompts</Tag>,
|
||||
render: () => <Tag>built-in</Tag>,
|
||||
},
|
||||
{
|
||||
title: 'Enable',
|
||||
dataIndex: 'enable',
|
||||
key: 'enable',
|
||||
// width: 80,
|
||||
render: (v: boolean = false, row: Record<string, any>, action: Record<string, any>) => (
|
||||
<Switch checked={v} onChange={(v) => action.setRecord({ ...row, enable: v }, 'enable')} />
|
||||
),
|
||||
@ -40,7 +36,6 @@ export const syncColumns = () => [
|
||||
title: 'Prompt',
|
||||
dataIndex: 'prompt',
|
||||
key: 'prompt',
|
||||
// width: 300,
|
||||
render: (v: string) => <span className="chat-prompts-val">{v}</span>,
|
||||
},
|
||||
];
|
||||
|
5
src/view/prompts/SyncPrompts/index.tsx
vendored
5
src/view/prompts/SyncPrompts/index.tsx
vendored
@ -12,7 +12,8 @@ import { fmtDate, chatRoot } from '@/utils';
|
||||
import { syncColumns } from './config';
|
||||
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';
|
||||
const promptsURL = 'https://raw.githubusercontent.com/f/awesome-chatgpt-prompts/main/prompts.csv';
|
||||
|
||||
export default function SyncPrompts() {
|
||||
const { rowSelection, selectedRowIDs } = useTableRowSelection();
|
||||
@ -83,7 +84,7 @@ export default function SyncPrompts() {
|
||||
</div>
|
||||
<div className="chat-table-tip">
|
||||
<div className="chat-sync-path">
|
||||
<FilePath url={promptsURL} content="f/awesome-chatgpt-prompts/prompts.csv" />
|
||||
<FilePath label="URL" url={promptsURL} content="f/awesome-chatgpt-prompts/prompts.csv" />
|
||||
<FilePath label="CACHE" paths="cache_prompts/chatgpt_prompts.json" />
|
||||
</div>
|
||||
{lastUpdated && (
|
||||
|
5
src/view/prompts/SyncRecord/config.tsx
vendored
5
src/view/prompts/SyncRecord/config.tsx
vendored
@ -7,7 +7,6 @@ export const syncColumns = () => [
|
||||
title: '/{cmd}',
|
||||
dataIndex: 'cmd',
|
||||
fixed: 'left',
|
||||
// width: 120,
|
||||
key: 'cmd',
|
||||
render: (_: string, row: Record<string, string>) => (
|
||||
<Tag color="#2a2a2a">/{row.cmd ? row.cmd : genCmd(row.act)}</Tag>
|
||||
@ -17,13 +16,11 @@ export const syncColumns = () => [
|
||||
title: 'Act',
|
||||
dataIndex: 'act',
|
||||
key: 'act',
|
||||
// width: 200,
|
||||
},
|
||||
{
|
||||
title: 'Tags',
|
||||
dataIndex: 'tags',
|
||||
key: 'tags',
|
||||
// width: 150,
|
||||
render: (v: string[]) => (
|
||||
<span className="chat-prompts-tags">
|
||||
{v?.map((i) => (
|
||||
@ -36,7 +33,6 @@ export const syncColumns = () => [
|
||||
title: 'Enable',
|
||||
dataIndex: 'enable',
|
||||
key: 'enable',
|
||||
// width: 80,
|
||||
render: (v: boolean = false, row: Record<string, any>, action: Record<string, any>) => (
|
||||
<Switch checked={v} onChange={(v) => action.setRecord({ ...row, enable: v }, 'enable')} />
|
||||
),
|
||||
@ -46,7 +42,6 @@ export const syncColumns = () => [
|
||||
title: 'Prompt',
|
||||
dataIndex: 'prompt',
|
||||
key: 'prompt',
|
||||
// width: 300,
|
||||
render: (v: string) => <span className="chat-prompts-val">{v}</span>,
|
||||
},
|
||||
];
|
||||
|
2
src/view/prompts/SyncRecord/index.tsx
vendored
2
src/view/prompts/SyncRecord/index.tsx
vendored
@ -73,7 +73,7 @@ export default function SyncRecord() {
|
||||
</div>
|
||||
<div className="chat-table-tip">
|
||||
<div className="chat-sync-path">
|
||||
<FilePath url={filePath} />
|
||||
<FilePath label="URL" url={filePath} />
|
||||
<FilePath label="CACHE" paths={`cache_prompts/${state?.id}.json`} />
|
||||
</div>
|
||||
{state?.last_updated && (
|
||||
|
12
src/view/prompts/UserCustom/Form.tsx
vendored
12
src/view/prompts/UserCustom/Form.tsx
vendored
@ -45,12 +45,6 @@ const UserCustomForm: ForwardRefRenderFunction<FormProps, UserCustomFormProps> =
|
||||
>
|
||||
<Input placeholder="Please enter the Act" {...DISABLE_AUTO_COMPLETE} />
|
||||
</Form.Item>
|
||||
<Form.Item label="Tags" name="tags">
|
||||
<Tags value={record?.tags} />
|
||||
</Form.Item>
|
||||
<Form.Item label="Enable" name="enable" valuePropName="checked">
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="Prompt"
|
||||
name="prompt"
|
||||
@ -58,6 +52,12 @@ const UserCustomForm: ForwardRefRenderFunction<FormProps, UserCustomFormProps> =
|
||||
>
|
||||
<Input.TextArea rows={4} placeholder="Please enter a prompt" {...DISABLE_AUTO_COMPLETE} />
|
||||
</Form.Item>
|
||||
<Form.Item label="Enable" name="enable" valuePropName="checked">
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item label="Tags" name="tags">
|
||||
<Tags value={record?.tags} />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
74
src/view/scripts/Editor.tsx
vendored
74
src/view/scripts/Editor.tsx
vendored
@ -1,32 +1,74 @@
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { FC, useState } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import Editor from '@monaco-editor/react';
|
||||
import { Breadcrumb, Tag, Popconfirm } from 'antd';
|
||||
import { ArrowLeftOutlined } from '@ant-design/icons';
|
||||
import { fs, shell, dialog, process } from '@tauri-apps/api';
|
||||
|
||||
interface MarkdownEditorProps {
|
||||
value?: string;
|
||||
import useInit from '@/hooks/useInit';
|
||||
import { getPath } from './config';
|
||||
|
||||
interface ScriptEditorProps {
|
||||
onChange?: (v: string) => void;
|
||||
mode?: string;
|
||||
}
|
||||
|
||||
const ScriptEditor: FC<MarkdownEditorProps> = ({
|
||||
value = 'console.log',
|
||||
onChange,
|
||||
mode = 'split',
|
||||
}) => {
|
||||
const [content, setContent] = useState(value);
|
||||
const ScriptEditor: FC<ScriptEditorProps> = ({ onChange }) => {
|
||||
const [filePath, setFilePath] = useState('');
|
||||
const [source, setSource] = useState('// write your script here\n');
|
||||
const location = useLocation();
|
||||
const state = location?.state;
|
||||
|
||||
useEffect(() => {
|
||||
setContent(value);
|
||||
onChange && onChange(value);
|
||||
}, [value]);
|
||||
useInit(async () => {
|
||||
const file = await getPath(state);
|
||||
setFilePath(file);
|
||||
setSource(await fs.readTextFile(file));
|
||||
});
|
||||
|
||||
const handleEdit = (e: any) => {
|
||||
setContent(e);
|
||||
setSource(e);
|
||||
onChange && onChange(e);
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
await fs.writeTextFile(filePath, source);
|
||||
const isOk = await dialog.ask(
|
||||
'The script will take effect after the application is restarted. Do you want to restart now?',
|
||||
{
|
||||
title: 'Script saved successfully',
|
||||
},
|
||||
);
|
||||
|
||||
if (isOk) {
|
||||
process.relaunch();
|
||||
}
|
||||
};
|
||||
|
||||
const handleReset = async () => {
|
||||
setSource(await fs.readTextFile(filePath));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="script-editor">
|
||||
<Editor language="js" value={content} onChange={handleEdit} />
|
||||
<Breadcrumb className="editor-task" separator="">
|
||||
<Breadcrumb.Item onClick={() => history.go(-1)}>
|
||||
<ArrowLeftOutlined />
|
||||
</Breadcrumb.Item>
|
||||
<Popconfirm
|
||||
placement="topRight"
|
||||
title="Are you sure you want to save the changes? It is a risky operation, but you can restore it through the sync button."
|
||||
onConfirm={handleSave}
|
||||
onCancel={handleReset}
|
||||
okText="Yes"
|
||||
cancelText="No"
|
||||
overlayStyle={{ width: 300 }}
|
||||
>
|
||||
<Tag className="editor-btn" color="#108ee9">
|
||||
Save
|
||||
</Tag>
|
||||
</Popconfirm>
|
||||
<Breadcrumb.Item onClick={() => shell.open(filePath)}>{filePath}</Breadcrumb.Item>
|
||||
</Breadcrumb>
|
||||
<Editor height="80vh" language="javascript" value={source} onChange={handleEdit} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
97
src/view/scripts/Head.tsx
vendored
97
src/view/scripts/Head.tsx
vendored
@ -1,97 +0,0 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Tag, Collapse, Tooltip } from 'antd';
|
||||
import { EditOutlined, FileSyncOutlined } from '@ant-design/icons';
|
||||
import { path, fs, shell } from '@tauri-apps/api';
|
||||
|
||||
import { chatRoot } from '@/utils';
|
||||
import useInit from '@/hooks/useInit';
|
||||
|
||||
export type ScriptInfo = {
|
||||
name: string;
|
||||
filePath: string;
|
||||
file: string;
|
||||
};
|
||||
|
||||
interface ScriptHeadProps {
|
||||
name: string;
|
||||
onEdit?: (data: ScriptInfo) => void;
|
||||
activeKey: string;
|
||||
}
|
||||
|
||||
export default function ScriptHead({ name, onEdit, activeKey }: ScriptHeadProps) {
|
||||
const [file, setFile] = useState('');
|
||||
const [filePath, setFilePath] = useState('');
|
||||
const [editing, setEdit] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (activeKey !== name) {
|
||||
setEdit(false);
|
||||
}
|
||||
}, [activeKey]);
|
||||
|
||||
useInit(async () => {
|
||||
const filePath = await path.join(await chatRoot(), 'scripts', name);
|
||||
setFilePath(filePath);
|
||||
const content = await fs.readTextFile(filePath);
|
||||
setFile(content);
|
||||
});
|
||||
|
||||
const handleGoFile = () => {
|
||||
shell.open(filePath);
|
||||
};
|
||||
|
||||
const handleEdit = async () => {
|
||||
setEdit(true);
|
||||
onEdit && onEdit({ name, filePath, file });
|
||||
};
|
||||
|
||||
const handleCancel = async () => {
|
||||
setEdit(false);
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
setEdit(false);
|
||||
};
|
||||
|
||||
const handleSync = async () => {};
|
||||
|
||||
const handleURL = async () => {
|
||||
shell.open(`https://github.com/lencx/ChatGPT/blob/main/scripts/${name}`);
|
||||
};
|
||||
|
||||
const version = '0.1.0';
|
||||
return (
|
||||
<>
|
||||
<span>
|
||||
<Tag color="orange">{version}</Tag>
|
||||
</span>
|
||||
{editing ? (
|
||||
<span>
|
||||
<Tag className="action-btn" onClick={handleCancel} color="default">
|
||||
Cancel
|
||||
</Tag>
|
||||
<Tag className="action-btn" onClick={handleSave} color="geekblue-inverse">
|
||||
Save
|
||||
</Tag>
|
||||
</span>
|
||||
) : (
|
||||
<Tag className="action-btn" title="Script Edit" onClick={handleEdit} color="blue-inverse">
|
||||
<EditOutlined />
|
||||
</Tag>
|
||||
)}
|
||||
<Tag className="action-btn" title="Script Sync" onClick={handleSync} color="orange-inverse">
|
||||
<FileSyncOutlined />
|
||||
</Tag>
|
||||
<span>
|
||||
<Tag className="file-path" color="blue" onClick={handleGoFile}>
|
||||
Path: {filePath}
|
||||
</Tag>
|
||||
</span>
|
||||
<span>
|
||||
<Tag className="file-path" color="green" onClick={handleURL}>
|
||||
URL: lencx/ChatGPT/scripts/{name}
|
||||
</Tag>
|
||||
</span>
|
||||
</>
|
||||
);
|
||||
}
|
55
src/view/scripts/config.tsx
vendored
55
src/view/scripts/config.tsx
vendored
@ -1,25 +1,37 @@
|
||||
import { useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Tag, Space, Popconfirm } from 'antd';
|
||||
import { ArrowRightOutlined } from '@ant-design/icons';
|
||||
import { path, shell } from '@tauri-apps/api';
|
||||
|
||||
import useInit from '@/hooks/useInit';
|
||||
import { fmtDate, chatRoot } from '@/utils';
|
||||
|
||||
export const scriptColumns = () => [
|
||||
export const scriptColumns = ({ scriptsMap }: any) => [
|
||||
{
|
||||
title: 'File Name',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
width: 120,
|
||||
render: (v: string) => <Tag>{v}</Tag>,
|
||||
fixed: 'left',
|
||||
width: 160,
|
||||
render: (v: string) => <Tag color={v === 'main.js' ? 'green' : 'default'}>{v}</Tag>,
|
||||
},
|
||||
{
|
||||
title: 'Version',
|
||||
dataIndex: 'version',
|
||||
key: 'version',
|
||||
width: 120,
|
||||
render: (v: string) => <Tag>{v}</Tag>,
|
||||
width: 200,
|
||||
render: (_: string, row: any) => {
|
||||
const next = scriptsMap?.[row.name]?.next_version;
|
||||
const curr = scriptsMap?.[row.name]?.curr_version;
|
||||
return (
|
||||
row.name !== 'main.js' && (
|
||||
<Space>
|
||||
{curr && <Tag style={{ marginRight: 0 }}>{curr}</Tag>}
|
||||
{next && next !== curr && <ArrowRightOutlined style={{ color: '#989898' }} />}
|
||||
{next && next !== curr && <Tag color="green-inverse">{next}</Tag>}
|
||||
</Space>
|
||||
)
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Path',
|
||||
@ -28,6 +40,14 @@ export const scriptColumns = () => [
|
||||
width: 200,
|
||||
render: (_: string, row: any) => <RenderPath row={row} />,
|
||||
},
|
||||
{
|
||||
title: 'Remote File',
|
||||
width: 200,
|
||||
render: (_: string, row: any) => {
|
||||
const uri = `https://raw.githubusercontent.com/lencx/ChatGPT/main/scripts/${row.name}`;
|
||||
return <a onClick={() => shell.open(uri)}>{uri}</a>;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Created',
|
||||
dataIndex: 'created',
|
||||
@ -38,28 +58,41 @@ export const scriptColumns = () => [
|
||||
{
|
||||
title: 'Action',
|
||||
fixed: 'right',
|
||||
width: 160,
|
||||
width: 100,
|
||||
render: (_: any, row: any, actions: any) => {
|
||||
const isExternal = row.name === 'main.js';
|
||||
|
||||
const next = scriptsMap?.[row.name]?.next_version;
|
||||
const curr = scriptsMap?.[row.name]?.curr_version;
|
||||
|
||||
return (
|
||||
<Space>
|
||||
<Link to={`/md/${row.id}`} state={row}>
|
||||
<Link
|
||||
to={`/scripts/${row.id}`}
|
||||
state={row}
|
||||
style={{ color: !isExternal ? '#ff4d4f' : '' }}
|
||||
>
|
||||
Edit
|
||||
</Link>
|
||||
{!isExternal && next && next !== curr && (
|
||||
<Popconfirm
|
||||
placement="topLeft"
|
||||
title="Are you sure you want to synchronize? It will overwrite all previous modifications made to this file."
|
||||
onConfirm={() => actions.setRecord(row, 'sync')}
|
||||
okText="Yes"
|
||||
cancelText="No"
|
||||
overlayStyle={{ width: 300 }}
|
||||
>
|
||||
<a>Sync</a>
|
||||
</Popconfirm>
|
||||
)}
|
||||
</Space>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const RenderPath = ({ row }: any) => {
|
||||
export const RenderPath = ({ row }: any) => {
|
||||
const [filePath, setFilePath] = useState('');
|
||||
useInit(async () => {
|
||||
setFilePath(await getPath(row));
|
||||
@ -68,5 +101,5 @@ const RenderPath = ({ row }: any) => {
|
||||
};
|
||||
|
||||
export const getPath = async (row: any) => {
|
||||
return (await path.join(await chatRoot(), 'notes', row.id)) + `.${row.ext}`;
|
||||
return await path.join(await chatRoot(), 'scripts', `${row.name}`);
|
||||
};
|
||||
|
10
src/view/scripts/index.scss
vendored
10
src/view/scripts/index.scss
vendored
@ -1,10 +0,0 @@
|
||||
.chatgpt-script {
|
||||
.ant-collapse-header {
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
}
|
||||
|
||||
.script-editor {
|
||||
height: 300px;
|
||||
}
|
85
src/view/scripts/index.tsx
vendored
85
src/view/scripts/index.tsx
vendored
@ -1,17 +1,15 @@
|
||||
import { useState } from 'react';
|
||||
import { Table, Tag } from 'antd';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Table } from 'antd';
|
||||
import { path, fs, invoke } from '@tauri-apps/api';
|
||||
|
||||
import useData from '@/hooks/useData';
|
||||
import useInit from '@/hooks/useInit';
|
||||
import useColumns from '@/hooks/useColumns';
|
||||
import { useTableRowSelection, TABLE_PAGINATION } from '@/hooks/useTable';
|
||||
import { TABLE_PAGINATION } from '@/hooks/useTable';
|
||||
import { scriptRoot } from '@/utils';
|
||||
import { scriptColumns } from './config';
|
||||
import ScriptHead, { type ScriptInfo } from './Head';
|
||||
import ScriptEditor from './Editor';
|
||||
import './index.scss';
|
||||
|
||||
// const { Panel } = Collapse;
|
||||
|
||||
const SCRIPTS = [
|
||||
'main.js',
|
||||
'core.js',
|
||||
'chat.js',
|
||||
'cmd.js',
|
||||
@ -22,51 +20,54 @@ const SCRIPTS = [
|
||||
];
|
||||
|
||||
export default function Scripts() {
|
||||
const [activeKey, setActiveKey] = useState('core.js');
|
||||
const [scriptsMap, setScriptsMap] = useState({});
|
||||
const { columns, ...opInfo } = useColumns(scriptColumns({ scriptsMap }));
|
||||
|
||||
const { columns, ...opInfo } = useColumns(scriptColumns());
|
||||
const handleInit = async () => {
|
||||
try {
|
||||
const manifestPath = await path.join(await scriptRoot(), 'manifest.json');
|
||||
const data = await fs.readTextFile(manifestPath);
|
||||
const { scripts } = JSON.parse(data);
|
||||
const infoMap: Record<string, any> = {};
|
||||
|
||||
const handleActiveKeyChange = (key: any) => {
|
||||
setActiveKey(key);
|
||||
for (const script of scripts) {
|
||||
const scriptInfo: any = await invoke('get_script_info', { name: script.name });
|
||||
infoMap[script.name] = {
|
||||
curr_version: scriptInfo?.version,
|
||||
next_version: script.version,
|
||||
};
|
||||
}
|
||||
setScriptsMap(infoMap);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
const panelHeadProps = {
|
||||
onEdit(data: ScriptInfo) {
|
||||
setActiveKey(data.name);
|
||||
},
|
||||
activeKey,
|
||||
};
|
||||
useInit(handleInit);
|
||||
|
||||
useEffect(() => {
|
||||
if (!opInfo.opType) return;
|
||||
(async () => {
|
||||
if (opInfo.opType === 'sync') {
|
||||
const isOk = await invoke('sync_scripts', { name: opInfo?.opRecord?.name });
|
||||
if (isOk) {
|
||||
await handleInit();
|
||||
opInfo.resetRecord();
|
||||
}
|
||||
}
|
||||
})();
|
||||
}, [opInfo.opType]);
|
||||
|
||||
return (
|
||||
<div className="chatgpt-script">
|
||||
<Table
|
||||
rowKey="name"
|
||||
scroll={{ x: 800 }}
|
||||
columns={columns}
|
||||
dataSource={SCRIPTS.map((i) => ({ name: i }))}
|
||||
{...TABLE_PAGINATION}
|
||||
pagination={false}
|
||||
/>
|
||||
{/* <Tabs
|
||||
items={SCRIPTS.map((i) => {
|
||||
return {
|
||||
label: <Tag>{i}</Tag>,
|
||||
key: i,
|
||||
children: <ScriptEditor />,
|
||||
}
|
||||
})}
|
||||
/> */}
|
||||
{/* <Collapse
|
||||
accordion
|
||||
collapsible="icon"
|
||||
activeKey={activeKey}
|
||||
onChange={handleActiveKeyChange}
|
||||
>
|
||||
{SCRIPTS.map((i) => {
|
||||
return (
|
||||
<Panel header={<ScriptHead name={i} {...panelHeadProps} />} key={i}>
|
||||
<ScriptEditor />
|
||||
</Panel>
|
||||
)
|
||||
})}
|
||||
</Collapse> */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user