1
0
mirror of https://github.com/lencx/ChatGPT.git synced 2024-10-01 01:06:13 -04:00

chore: scripts

This commit is contained in:
lencx 2023-05-25 09:52:33 +08:00
parent 882593479b
commit ee0829d8db
39 changed files with 1370 additions and 1177 deletions

View File

@ -5,7 +5,7 @@
</p> </p>
[![English badge](https://img.shields.io/badge/%E8%8B%B1%E6%96%87-English-blue)](./README.md) [![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) [![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) [![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_) [![lencx](https://img.shields.io/badge/follow-lencx__-blue?style=flat&logo=Twitter)](https://twitter.com/lencx_)

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,5 @@
# ChatGPT Scripts # 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
View File

@ -4,7 +4,7 @@
* @url https://github.com/lencx/ChatGPT/tree/main/scripts/chat.js * @url https://github.com/lencx/ChatGPT/tree/main/scripts/chat.js
*/ */
var chatInit = (() => { function chatInit() {
const ICONS = { 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>`, 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>`, 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; currentIndex = -1;
}; };
} }
}
return { init };
})();
if (document.readyState === 'complete' || document.readyState === 'interactive') { if (document.readyState === 'complete' || document.readyState === 'interactive') {
chatInit.init(); chatInit();
} else { } else {
document.addEventListener('DOMContentLoaded', chatInit.init); document.addEventListener('DOMContentLoaded', chatInit);
} }

346
scripts/cmd.js vendored
View File

@ -4,7 +4,7 @@
* @url https://github.com/lencx/ChatGPT/tree/main/scripts/cmd.js * @url https://github.com/lencx/ChatGPT/tree/main/scripts/cmd.js
*/ */
function init() { function cmdInit() {
const styleDom = document.createElement('style'); const styleDom = document.createElement('style');
styleDom.innerHTML = `form { styleDom.innerHTML = `form {
position: relative; position: relative;
@ -140,203 +140,203 @@ function init() {
subtree: true, subtree: true,
}); });
}, 300); }, 300);
}
async function cmdTip() { async function cmdTip() {
initDom(); initDom();
const chatPromptJson = (await invoke('get_chat_prompt_cmd')) || {}; const chatPromptJson = (await invoke('get_chat_prompt_cmd')) || {};
const data = chatPromptJson.data; const data = chatPromptJson.data;
if (data.length <= 0) return; if (data.length <= 0) return;
let promptDom = document.querySelector('.chat-prompt-cmd-list'); let promptDom = document.querySelector('.chat-prompt-cmd-list');
if (!promptDom) { if (!promptDom) {
const dom = document.createElement('div'); const dom = document.createElement('div');
dom.classList.add('chat-prompt-cmd-list'); dom.classList.add('chat-prompt-cmd-list');
document.querySelector('form').appendChild(dom); document.querySelector('form').appendChild(dom);
promptDom = document.querySelector('.chat-prompt-cmd-list'); promptDom = document.querySelector('.chat-prompt-cmd-list');
// fix: tray window // fix: tray window
if (__TAURI_METADATA__.__currentWindow.label === 'tray') { if (__TAURI_METADATA__.__currentWindow.label === 'tray') {
promptDom.style.bottom = '54px'; promptDom.style.bottom = '54px';
}
const itemDom = (v) =>
`<div class="cmd-item" title="${v.prompt}" data-cmd="${
v.cmd
}" data-prompt="${encodeURIComponent(v.prompt)}"><b title="${v.cmd}">/${v.cmd}</b><i>${
v.act
}</i></div>`;
const renderList = (v) => {
initDom();
promptDom.innerHTML = `<div>${v.map(itemDom).join('')}</div>`;
window.__CHAT_CMD_PROMPT__ = v[0]?.prompt.trim();
window.__CHAT_CMD__ = v[0]?.cmd.trim();
window.__cmd_list = promptDom.querySelectorAll('.cmd-item');
window.__cmd_index = 0;
window.__cmd_list[window.__cmd_index].classList.add('selected');
};
const setPrompt = (v = '') => {
if (v.trim()) {
window.__CHAT_CMD_PROMPT__ = window.__CHAT_CMD_PROMPT__?.replace(
/\{([^{}]*)\}/,
`{${v.trim()}}`,
);
} }
};
const searchInput = document.querySelector('form textarea');
// Enter a command starting with `/` and press a space to automatically fill `chatgpt prompt`. const itemDom = (v) =>
// If more than one command appears in the search results, the first one will be used by default. `<div class="cmd-item" title="${v.prompt}" data-cmd="${
function cmdKeydown(event) { v.cmd
if (!window.__CHAT_CMD_PROMPT__) { }" data-prompt="${encodeURIComponent(v.prompt)}"><b title="${v.cmd}">/${v.cmd}</b><i>${
if ( v.act
!event.shiftKey && }</i></div>`;
event.keyCode === 13 && const renderList = (v) => {
__TAURI_METADATA__.__currentWindow.label === 'tray' initDom();
) { promptDom.innerHTML = `<div>${v.map(itemDom).join('')}</div>`;
const btn = document.querySelector('form button'); window.__CHAT_CMD_PROMPT__ = v[0]?.prompt.trim();
if (btn) btn.click(); window.__CHAT_CMD__ = v[0]?.cmd.trim();
window.__cmd_list = promptDom.querySelectorAll('.cmd-item');
window.__cmd_index = 0;
window.__cmd_list[window.__cmd_index].classList.add('selected');
};
const setPrompt = (v = '') => {
if (v.trim()) {
window.__CHAT_CMD_PROMPT__ = window.__CHAT_CMD_PROMPT__?.replace(
/\{([^{}]*)\}/,
`{${v.trim()}}`,
);
}
};
const searchInput = document.querySelector('form textarea');
// Enter a command starting with `/` and press a space to automatically fill `chatgpt prompt`.
// If more than one command appears in the search results, the first one will be used by default.
function cmdKeydown(event) {
if (!window.__CHAT_CMD_PROMPT__) {
if (
!event.shiftKey &&
event.keyCode === 13 &&
__TAURI_METADATA__.__currentWindow.label === 'tray'
) {
const btn = document.querySelector('form button');
if (btn) btn.click();
event.preventDefault();
}
return;
}
// ------------------ Keyboard scrolling (ArrowUp | ArrowDown) --------------------------
if (event.keyCode === 38 && window.__cmd_index > 0) {
// ArrowUp
window.__cmd_list[window.__cmd_index].classList.remove('selected');
window.__cmd_index = window.__cmd_index - 1;
window.__cmd_list[window.__cmd_index].classList.add('selected');
window.__CHAT_CMD_PROMPT__ = decodeURIComponent(
window.__cmd_list[window.__cmd_index].getAttribute('data-prompt'),
);
searchInput.value = `/${window.__cmd_list[window.__cmd_index].getAttribute('data-cmd')}`;
event.preventDefault(); event.preventDefault();
} }
return;
}
// ------------------ Keyboard scrolling (ArrowUp | ArrowDown) -------------------------- if (event.keyCode === 40 && window.__cmd_index < window.__cmd_list.length - 1) {
if (event.keyCode === 38 && window.__cmd_index > 0) { // ArrowDown
// ArrowUp window.__cmd_list[window.__cmd_index].classList.remove('selected');
window.__cmd_list[window.__cmd_index].classList.remove('selected'); window.__cmd_index = window.__cmd_index + 1;
window.__cmd_index = window.__cmd_index - 1; window.__cmd_list[window.__cmd_index].classList.add('selected');
window.__cmd_list[window.__cmd_index].classList.add('selected'); window.__CHAT_CMD_PROMPT__ = decodeURIComponent(
window.__CHAT_CMD_PROMPT__ = decodeURIComponent( window.__cmd_list[window.__cmd_index].getAttribute('data-prompt'),
window.__cmd_list[window.__cmd_index].getAttribute('data-prompt'), );
); searchInput.value = `/${window.__cmd_list[window.__cmd_index].getAttribute('data-cmd')}`;
searchInput.value = `/${window.__cmd_list[window.__cmd_index].getAttribute('data-cmd')}`; event.preventDefault();
event.preventDefault(); }
}
if (event.keyCode === 40 && window.__cmd_index < window.__cmd_list.length - 1) { const containerHeight = promptDom.offsetHeight;
// ArrowDown const itemHeight = window.__cmd_list[0].offsetHeight + 1;
window.__cmd_list[window.__cmd_index].classList.remove('selected');
window.__cmd_index = window.__cmd_index + 1;
window.__cmd_list[window.__cmd_index].classList.add('selected');
window.__CHAT_CMD_PROMPT__ = decodeURIComponent(
window.__cmd_list[window.__cmd_index].getAttribute('data-prompt'),
);
searchInput.value = `/${window.__cmd_list[window.__cmd_index].getAttribute('data-cmd')}`;
event.preventDefault();
}
const containerHeight = promptDom.offsetHeight; const itemTop = window.__cmd_list[window.__cmd_index].offsetTop;
const itemHeight = window.__cmd_list[0].offsetHeight + 1; const itemBottom = itemTop + itemHeight;
if (itemTop < promptDom.scrollTop || itemBottom > promptDom.scrollTop + containerHeight) {
promptDom.scrollTop = itemTop;
}
const itemTop = window.__cmd_list[window.__cmd_index].offsetTop; // ------------------ TAB key replaces `{q}` tag content -------------------------------
const itemBottom = itemTop + itemHeight; // feat: https://github.com/lencx/ChatGPT/issues/54
if (itemTop < promptDom.scrollTop || itemBottom > promptDom.scrollTop + containerHeight) { if (event.keyCode === 9 && !window.__CHAT_STATUS__) {
promptDom.scrollTop = itemTop; const strGroup = window.__CHAT_CMD_PROMPT__.match(/\{([^{}]*)\}/) || [];
}
// ------------------ TAB key replaces `{q}` tag content ------------------------------- if (strGroup[1]) {
// feat: https://github.com/lencx/ChatGPT/issues/54 searchInput.value = `/${window.__CHAT_CMD__}` + ` {${strGroup[1]}}` + ' |-> ';
if (event.keyCode === 9 && !window.__CHAT_STATUS__) { window.__CHAT_STATUS__ = 1;
const strGroup = window.__CHAT_CMD_PROMPT__.match(/\{([^{}]*)\}/) || []; } else {
searchInput.value = window.__CHAT_CMD_PROMPT__;
initDom();
}
event.preventDefault();
}
if (strGroup[1]) { if (window.__CHAT_STATUS__ === 1 && event.keyCode === 9) {
searchInput.value = `/${window.__CHAT_CMD__}` + ` {${strGroup[1]}}` + ' |-> '; // TAB
window.__CHAT_STATUS__ = 1; const data = searchInput.value.split('|->');
} else { if (data[1]?.trim()) {
setPrompt(data[1]);
window.__CHAT_STATUS__ = 2;
}
event.preventDefault();
}
// input text
if (window.__CHAT_STATUS__ === 2 && event.keyCode === 9) {
// TAB
searchInput.value = window.__CHAT_CMD_PROMPT__; searchInput.value = window.__CHAT_CMD_PROMPT__;
promptDom.innerHTML = '';
delete window.__CHAT_STATUS__;
event.preventDefault();
}
// ------------------ type in a space to complete the fill ------------------------------------
if (event.keyCode === 32) {
searchInput.value = window.__CHAT_CMD_PROMPT__;
promptDom.innerHTML = '';
delete window.__CHAT_CMD_PROMPT__;
}
// ------------------ send --------------------------------------------------------------------
if (event.keyCode === 13 && window.__CHAT_CMD_PROMPT__) {
// Enter
const data = searchInput.value.split('|->');
setPrompt(data[1]);
searchInput.value = window.__CHAT_CMD_PROMPT__;
initDom();
event.preventDefault();
}
}
searchInput.removeEventListener('keydown', cmdKeydown, { capture: true });
searchInput.addEventListener('keydown', cmdKeydown, { capture: true });
function cmdInput() {
if (searchInput.value === '') {
initDom(); initDom();
} }
event.preventDefault();
}
if (window.__CHAT_STATUS__ === 1 && event.keyCode === 9) { if (window.__CHAT_STATUS__) return;
// TAB
const data = searchInput.value.split('|->'); const query = searchInput.value;
if (data[1]?.trim()) { if (!query || !/^\//.test(query)) {
setPrompt(data[1]); initDom();
window.__CHAT_STATUS__ = 2; return;
}
// all cmd result
if (query === '/') {
renderList(data);
return;
}
const result = data.filter((i) => new RegExp(query.substring(1)).test(i.cmd));
if (result.length > 0) {
renderList(result);
} else {
initDom();
} }
event.preventDefault();
}
// input text
if (window.__CHAT_STATUS__ === 2 && event.keyCode === 9) {
// TAB
searchInput.value = window.__CHAT_CMD_PROMPT__;
promptDom.innerHTML = '';
delete window.__CHAT_STATUS__;
event.preventDefault();
}
// ------------------ type in a space to complete the fill ------------------------------------
if (event.keyCode === 32) {
searchInput.value = window.__CHAT_CMD_PROMPT__;
promptDom.innerHTML = '';
delete window.__CHAT_CMD_PROMPT__;
}
// ------------------ send --------------------------------------------------------------------
if (event.keyCode === 13 && window.__CHAT_CMD_PROMPT__) {
// Enter
const data = searchInput.value.split('|->');
setPrompt(data[1]);
searchInput.value = window.__CHAT_CMD_PROMPT__;
initDom();
event.preventDefault();
} }
searchInput.removeEventListener('input', cmdInput);
searchInput.addEventListener('input', cmdInput);
} }
searchInput.removeEventListener('keydown', cmdKeydown, { capture: true }); }
searchInput.addEventListener('keydown', cmdKeydown, { capture: true });
function cmdInput() { function initDom() {
if (searchInput.value === '') { const promptDom = document.querySelector('.chat-prompt-cmd-list');
initDom(); if (promptDom) {
} promptDom.innerHTML = '';
if (window.__CHAT_STATUS__) return;
const query = searchInput.value;
if (!query || !/^\//.test(query)) {
initDom();
return;
}
// all cmd result
if (query === '/') {
renderList(data);
return;
}
const result = data.filter((i) => new RegExp(query.substring(1)).test(i.cmd));
if (result.length > 0) {
renderList(result);
} else {
initDom();
}
} }
searchInput.removeEventListener('input', cmdInput); delete window.__CHAT_CMD_PROMPT__;
searchInput.addEventListener('input', cmdInput); delete window.__CHAT_CMD__;
delete window.__CHAT_STATUS__;
delete window.__cmd_list;
delete window.__cmd_index;
} }
} }
function initDom() {
const promptDom = document.querySelector('.chat-prompt-cmd-list');
if (promptDom) {
promptDom.innerHTML = '';
}
delete window.__CHAT_CMD_PROMPT__;
delete window.__CHAT_CMD__;
delete window.__CHAT_STATUS__;
delete window.__cmd_list;
delete window.__cmd_index;
}
if (document.readyState === 'complete' || document.readyState === 'interactive') { if (document.readyState === 'complete' || document.readyState === 'interactive') {
init(); cmdInit();
} else { } else {
document.addEventListener('DOMContentLoaded', init); document.addEventListener('DOMContentLoaded', cmdInit);
} }

11
scripts/core.js vendored
View File

@ -4,7 +4,7 @@
* @url https://github.com/lencx/ChatGPT/tree/main/scripts/core.js * @url https://github.com/lencx/ChatGPT/tree/main/scripts/core.js
*/ */
var coreInit = (() => { function coreInit() {
const uid = () => window.crypto.getRandomValues(new Uint32Array(1))[0]; const uid = () => window.crypto.getRandomValues(new Uint32Array(1))[0];
function transformCallback(callback = () => {}, once = false) { function transformCallback(callback = () => {}, once = false) {
const identifier = uid(); const identifier = uid();
@ -21,6 +21,7 @@ var coreInit = (() => {
}); });
return identifier; return identifier;
} }
async function invoke(cmd, args) { async function invoke(cmd, args) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (!window.__TAURI_POST_MESSAGE__) reject('__TAURI_POST_MESSAGE__ does not exist!'); if (!window.__TAURI_POST_MESSAGE__) reject('__TAURI_POST_MESSAGE__ does not exist!');
@ -226,11 +227,11 @@ var coreInit = (() => {
return { startLoading, stopLoading }; return { startLoading, stopLoading };
} }
return { init }; init();
})(); }
if (document.readyState === 'complete' || document.readyState === 'interactive') { if (document.readyState === 'complete' || document.readyState === 'interactive') {
coreInit.init(); coreInit();
} else { } else {
document.addEventListener('DOMContentLoaded', coreInit.init); document.addEventListener('DOMContentLoaded', coreInit);
} }

6
scripts/dalle2.js vendored
View File

@ -4,7 +4,7 @@
* @url https://github.com/lencx/ChatGPT/tree/main/scripts/dalle2.js * @url https://github.com/lencx/ChatGPT/tree/main/scripts/dalle2.js
*/ */
function init() { function dalle2Init() {
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;
@ -35,7 +35,7 @@ function init() {
} }
if (document.readyState === 'complete' || document.readyState === 'interactive') { if (document.readyState === 'complete' || document.readyState === 'interactive') {
init(); dalle2Init();
} else { } else {
document.addEventListener('DOMContentLoaded', init); document.addEventListener('DOMContentLoaded', dalle2Init);
} }

10
scripts/export.js vendored
View File

@ -4,7 +4,7 @@
* @url https://github.com/lencx/ChatGPT/tree/main/scripts/export.js * @url https://github.com/lencx/ChatGPT/tree/main/scripts/export.js
*/ */
async function init() { async function exportInit() {
if (window.location.pathname === '/auth/login') return; if (window.location.pathname === '/auth/login') return;
const buttonOuterHTMLFallback = `<button class="btn flex justify-center gap-2 btn-neutral">Try Again</button>`; const buttonOuterHTMLFallback = `<button class="btn flex justify-center gap-2 btn-neutral">Try Again</button>`;
removeButtons(); removeButtons();
@ -303,7 +303,7 @@ async function init() {
const hours = String(now.getHours()).padStart(2, '0'); const hours = String(now.getHours()).padStart(2, '0');
const minutes = String(now.getMinutes()).padStart(2, '0'); const minutes = String(now.getMinutes()).padStart(2, '0');
const seconds = String(now.getSeconds()).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; return formattedDateTime;
} }
@ -315,10 +315,10 @@ async function init() {
} }
} }
window.addEventListener('resize', init); window.addEventListener('resize', exportInit);
if (document.readyState === 'complete' || document.readyState === 'interactive') { if (document.readyState === 'complete' || document.readyState === 'interactive') {
init(); exportInit();
} else { } else {
document.addEventListener('DOMContentLoaded', init); document.addEventListener('DOMContentLoaded', exportInit);
} }

5
scripts/main.js vendored Normal file
View File

@ -0,0 +1,5 @@
// *** ChatGPT User Script ***
// @github: https://github.com/lencx/ChatGPT
// @path: $HOME/.chatgpt/scripts/main.js
console.log('Hello, ChatGPT!');

View File

@ -12,6 +12,10 @@
"name": "cmd.js", "name": "cmd.js",
"version": "0.1.0" "version": "0.1.0"
}, },
{
"name": "chat.js",
"version": "0.1.0"
},
{ {
"name": "core.js", "name": "core.js",
"version": "0.1.0" "version": "0.1.0"

View File

@ -4,7 +4,7 @@
* @url https://github.com/lencx/ChatGPT/tree/main/scripts/popup.core.js * @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')) || {}; const chatConf = (await invoke('get_app_conf')) || {};
if (!chatConf.popup_search) return; if (!chatConf.popup_search) return;
if (!window.FloatingUIDOM) return; if (!window.FloatingUIDOM) return;
@ -77,7 +77,7 @@ async function init() {
} }
if (document.readyState === 'complete' || document.readyState === 'interactive') { if (document.readyState === 'complete' || document.readyState === 'interactive') {
init(); popupCoreInit();
} else { } else {
document.addEventListener('DOMContentLoaded', init); document.addEventListener('DOMContentLoaded', popupCoreInit);
} }

View File

@ -9,8 +9,6 @@ use std::{collections::HashMap, fs, path::PathBuf, vec};
use tauri::{api, command, AppHandle, Manager}; use tauri::{api, command, AppHandle, Manager};
use walkdir::WalkDir; use walkdir::WalkDir;
use super::fs_extra::Error;
#[command] #[command]
pub fn get_chat_prompt_cmd() -> serde_json::Value { pub fn get_chat_prompt_cmd() -> serde_json::Value {
let path = utils::app_root().join("chat.prompt.cmd.json"); 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(), act: i.act.clone(),
prompt: i.prompt.clone(), prompt: i.prompt.clone(),
tags: vec!["chatgpt-prompts".to_string()], tags: vec!["awesome-chatgpt-prompts".to_string()],
enable: true, enable: true,
}) })
.collect::<Vec<PromptRecord>>(); .collect::<Vec<PromptRecord>>();
let data2 = transformed_data;
let prompts = utils::app_root().join("chat.prompt.json"); let prompts = utils::app_root().join("chat.prompt.json");
let prompt_cmd = utils::app_root().join("chat.prompt.cmd.json"); let prompt_cmd = utils::app_root().join("chat.prompt.cmd.json");
let chatgpt_prompts = utils::app_root() 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 // chatgpt_prompts.json
fs::write( fs::write(
chatgpt_prompts, chatgpt_prompts,
serde_json::to_string_pretty(&data).unwrap(), serde_json::to_string_pretty(&transformed_data).unwrap(),
) )
.unwrap(); .unwrap();
let cmd_data = cmd_list(); 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.clone(), "core");
window::cmd::window_reload(app, "tray"); window::cmd::window_reload(app, "tray");
return Some(data2); return Some(transformed_data);
} }
} }

View File

@ -93,9 +93,6 @@ pub fn init() -> Menu {
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
hide_dock_icon_menu.into(), hide_dock_icon_menu.into(),
system_tray_menu.into(), system_tray_menu.into(),
CustomMenuItem::new("inject_script", "Inject Script")
.accelerator("CmdOrCtrl+J")
.into(),
MenuItem::Separator.into(), MenuItem::Separator.into(),
Submenu::new( Submenu::new(
"Theme", "Theme",
@ -219,7 +216,6 @@ pub fn init() -> Menu {
pub fn menu_handler(event: WindowMenuEvent<tauri::Wry>) { pub fn menu_handler(event: WindowMenuEvent<tauri::Wry>) {
let win = Some(event.window()).unwrap(); let win = Some(event.window()).unwrap();
let app = win.app_handle(); 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_id = event.menu_item_id();
let menu_handle = win.menu_handle(); let menu_handle = win.menu_handle();
@ -239,7 +235,6 @@ pub fn menu_handler(event: WindowMenuEvent<tauri::Wry>) {
// Preferences // Preferences
"control_center" => window::cmd::control_window(app, "control".into()), "control_center" => window::cmd::control_window(app, "control".into()),
"restart" => tauri::api::process::restart(&app.env()), "restart" => tauri::api::process::restart(&app.env()),
"inject_script" => open(&app, &script_path),
"go_conf" => utils::open_file(utils::app_root()), "go_conf" => utils::open_file(utils::app_root()),
"clear_conf" => utils::clear_conf(&app), "clear_conf" => utils::clear_conf(&app),
"app_website" => window::cmd::wa_window( "app_website" => window::cmd::wa_window(

View File

@ -4,4 +4,5 @@ pub mod gpt;
pub mod menu; pub mod menu;
pub mod script; pub mod script;
pub mod setup; pub mod setup;
pub mod template;
pub mod window; pub mod window;

View File

@ -1,4 +1,4 @@
use crate::utils::{app_root, exists}; use crate::utils::{app_root, create_file, exists};
use log::error; use log::error;
use log::info; use log::info;
use regex::Regex; use regex::Regex;
@ -6,10 +6,13 @@ use serde_json::{from_str, json, Value};
use std::fs; use std::fs;
use tauri::Manager; 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> { 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()) { if exist_scripts("manifest.json".into()) {
let compare = compare_nested_json_objects( let compare = compare_nested_json_objects(
@ -39,8 +42,16 @@ pub async fn init_script(app: tauri::AppHandle) -> anyhow::Result<(), reqwest::E
Ok(()) Ok(())
} }
pub fn parse_script(name: String) -> serde_json::Value { pub fn parse_script(name: String) -> Option<serde_json::Value> {
let code = &fs::read_to_string(name).unwrap(); 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_name = Regex::new(r"@name\s+(.*?)\n").unwrap();
let re_version = Regex::new(r"@version\s+(.*?)\n").unwrap(); let re_version = Regex::new(r"@version\s+(.*?)\n").unwrap();
let re_url = Regex::new(r"@url\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 version = String::new();
let mut url = 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(); 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(); 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(); 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, "url": url,
}); });
json_data Some(json_data)
} }
pub fn exist_scripts(file: String) -> bool { 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) { pub fn create_chatgpt_scripts(file: String, body: String) {
let script_file = app_root().join("scripts").join(file); let script_file = app_root().join("scripts").join(file);
info!("script_file: {:?}", script_file); match create_file(&script_file) {
fs::write(&script_file, body).unwrap(); 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 { 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, _ => 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
}
}

View File

@ -1,4 +1,8 @@
use crate::{app, conf::AppConf, utils}; use crate::{
app,
conf::AppConf,
utils::{self, load_script},
};
use log::{error, info}; use log::{error, info};
use tauri::{utils::config::WindowUrl, window::WindowBuilder, App, GlobalShortcutManager, Manager}; use tauri::{utils::config::WindowUrl, window::WindowBuilder, App, GlobalShortcutManager, Manager};
use wry::application::accelerator::Accelerator; 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 url = app_conf.main_origin.to_string();
let theme = AppConf::theme_mode(); let theme = AppConf::theme_mode();
app::template::Template::new(utils::app_root().join("scripts"));
let handle = app.app_handle(); let handle = app.app_handle();
tauri::async_runtime::spawn(async move { tauri::async_runtime::spawn(async move {
app::script::init_script(handle) app::script::init_script(handle)
@ -71,7 +77,7 @@ pub fn init(app: &mut App) -> Result<(), Box<dyn std::error::Error>> {
.theme(Some(theme)) .theme(Some(theme))
.always_on_top(app_conf2.stay_on_top) .always_on_top(app_conf2.stay_on_top)
.initialization_script(&utils::user_script()) .initialization_script(&utils::user_script())
.initialization_script(include_str!("../../../scripts/core.js")) .initialization_script(&load_script("core.js"))
.user_agent(&app_conf2.ua_window); .user_agent(&app_conf2.ua_window);
#[cfg(target_os = "macos")] #[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/jspdf.js"))
.initialization_script(include_str!("../vendors/turndown.js")) .initialization_script(include_str!("../vendors/turndown.js"))
.initialization_script(include_str!("../vendors/turndown-plugin-gfm.js")) .initialization_script(include_str!("../vendors/turndown-plugin-gfm.js"))
.initialization_script(include_str!("../../../scripts/popup.core.js")) .initialization_script(&load_script("popup.core.js"))
.initialization_script(include_str!("../../../scripts/export.js")) .initialization_script(&load_script("export.js"))
.initialization_script(include_str!("../../../scripts/markdown.export.js")) .initialization_script(&load_script("markdown.export.js"))
.initialization_script(include_str!("../../../scripts/cmd.js")) .initialization_script(&load_script("cmd.js"))
.initialization_script(include_str!("../../../scripts/chat.js")) .initialization_script(&load_script("chat.js"))
} }
main_win.build().unwrap(); main_win.build().unwrap();

View 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(())
}

View File

@ -1,4 +1,7 @@
use crate::{conf::AppConf, utils}; use crate::{
conf::AppConf,
utils::{self, load_script},
};
use log::info; use log::info;
use std::time::SystemTime; use std::time::SystemTime;
use tauri::{utils::config::WindowUrl, window::WindowBuilder, Manager}; use tauri::{utils::config::WindowUrl, window::WindowBuilder, Manager};
@ -23,16 +26,16 @@ pub fn tray_window(handle: &tauri::AppHandle) {
.always_on_top(true) .always_on_top(true)
.theme(Some(theme)) .theme(Some(theme))
.initialization_script(&utils::user_script()) .initialization_script(&utils::user_script())
.initialization_script(include_str!("../../../scripts/core.js")) .initialization_script(&load_script("core.js"))
.user_agent(&app_conf.ua_tray); .user_agent(&app_conf.ua_tray);
if app_conf.tray_origin == "https://chat.openai.com" && !app_conf.tray_dashboard { if app_conf.tray_origin == "https://chat.openai.com" && !app_conf.tray_dashboard {
tray_win = tray_win tray_win = tray_win
.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!("../../../scripts/cmd.js")) .initialization_script(&load_script("cmd.js"))
.initialization_script(include_str!("../../../scripts/chat.js")) .initialization_script(&load_script("chat.js"))
.initialization_script(include_str!("../../../scripts/popup.core.js")) .initialization_script(&load_script("popup.core.js"))
} }
tray_win.build().unwrap().hide().unwrap(); tray_win.build().unwrap().hide().unwrap();
@ -78,9 +81,9 @@ 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(Some(theme)) .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(&query)
.initialization_script(include_str!("../../../scripts/dalle2.js"))
.build() .build()
.unwrap(); .unwrap();
}); });
@ -161,7 +164,7 @@ pub mod cmd {
tauri::async_runtime::spawn(async move { tauri::async_runtime::spawn(async move {
tauri::WindowBuilder::new(&app, label, tauri::WindowUrl::App(url.parse().unwrap())) tauri::WindowBuilder::new(&app, label, tauri::WindowUrl::App(url.parse().unwrap()))
.initialization_script(&script.unwrap_or_default()) .initialization_script(&script.unwrap_or_default())
.initialization_script(include_str!("../../../scripts/core.js")) .initialization_script(&load_script("core.js"))
.title(title) .title(title)
.inner_size(960.0, 700.0) .inner_size(960.0, 700.0)
.resizable(true) .resizable(true)

View File

@ -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 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 SCRIPTS_MANIFEST: &str = pub const SCRIPTS_DIR: &str = "https://raw.githubusercontent.com/lencx/ChatGPT/main/scripts/";
"https://raw.githubusercontent.com/lencx/ChatGPT/main/scripts/manifest.json";
pub const APP_CONF_PATH: &str = "chat.conf.json"; pub const APP_CONF_PATH: &str = "chat.conf.json";
pub const CHATGPT_URL: &str = "https://chat.openai.com"; pub const CHATGPT_URL: &str = "https://chat.openai.com";

View File

@ -7,7 +7,7 @@ mod app;
mod conf; mod conf;
mod utils; 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 conf::AppConf;
use tauri_plugin_autostart::MacosLauncher; use tauri_plugin_autostart::MacosLauncher;
use tauri_plugin_log::{ use tauri_plugin_log::{
@ -76,6 +76,8 @@ async fn main() {
conf::cmd::form_confirm, conf::cmd::form_confirm,
conf::cmd::form_cancel, conf::cmd::form_cancel,
conf::cmd::form_msg, conf::cmd::form_msg,
script::cmd::sync_scripts,
script::cmd::get_script_info,
window::cmd::wa_window, window::cmd::wa_window,
window::cmd::control_window, window::cmd::control_window,
window::cmd::window_reload, window::cmd::window_reload,

View File

@ -4,7 +4,7 @@ use regex::Regex;
use serde_json::Value; use serde_json::Value;
use std::{ use std::{
collections::HashMap, collections::HashMap,
fs::{self, File}, fs,
path::{Path, PathBuf}, path::{Path, PathBuf},
process::Command, process::Command,
}; };
@ -25,11 +25,15 @@ pub fn exists(path: &Path) -> bool {
Path::new(path).exists() Path::new(path).exists()
} }
pub fn create_file(path: &Path) -> Result<File> { pub fn create_file<P: AsRef<Path>>(filename: P) -> Result<()> {
if let Some(p) = path.parent() { let filename = filename.as_ref();
fs::create_dir_all(p)? 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() { 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 { 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!( format!(
"window.addEventListener('DOMContentLoaded', function() {{\n{}\n}})", "window.addEventListener('DOMContentLoaded', function() {{\n{}\n}})",
user_script_content 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) { pub fn open_file(path: PathBuf) {
let pathname = convert_path(path.to_str().unwrap()); let pathname = convert_path(path.to_str().unwrap());
info!("open_file: {}", pathname); info!("open_file: {}", pathname);

View File

@ -16,7 +16,8 @@ const FilePath: FC<FilePathProps> = ({ className, label = 'PATH', paths = '', ur
const [filePath, setPath] = useState(''); const [filePath, setPath] = useState('');
useEffect(() => { useEffect(() => {
if (!path && !url) return; if (!(paths || url)) return;
(async () => { (async () => {
if (url) { if (url) {
setPath(url); setPath(url);

View File

@ -74,6 +74,7 @@ export const EditRow: FC<EditRowProps> = ({ rowKey, row, actions }) => {
onChange={handleChange} onChange={handleChange}
{...DISABLE_AUTO_COMPLETE} {...DISABLE_AUTO_COMPLETE}
onPressEnter={handleSave} onPressEnter={handleSave}
onBlur={handleSave}
/> />
) : ( ) : (
<div className="rowedit" onClick={handleEdit}> <div className="rowedit" onClick={handleEdit}>

22
src/main.scss vendored
View File

@ -114,3 +114,25 @@ body,
cursor: pointer; 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
View File

@ -14,6 +14,7 @@ import type { MenuProps } from 'antd';
import Settings from '@/view/settings'; import Settings from '@/view/settings';
import About from '@/view/about'; import About from '@/view/about';
import Scripts from '@/view/scripts'; import Scripts from '@/view/scripts';
import ScriptsEditor from '@/view/scripts/Editor';
import UserCustom from '@/view/prompts/UserCustom'; import UserCustom from '@/view/prompts/UserCustom';
import SyncPrompts from '@/view/prompts/SyncPrompts'; import SyncPrompts from '@/view/prompts/SyncPrompts';
import SyncCustom from '@/view/prompts/SyncCustom'; import SyncCustom from '@/view/prompts/SyncCustom';
@ -103,6 +104,11 @@ export const routes: Array<ChatRouteObject> = [
icon: <CodeOutlined />, icon: <CodeOutlined />,
}, },
}, },
{
path: '/scripts/:id',
element: <ScriptsEditor />,
hideMenu: true,
},
{ {
path: '/about', path: '/about',
element: <About />, element: <About />,

4
src/utils.ts vendored
View File

@ -23,6 +23,10 @@ export const chatRoot = async () => {
return join(await homeDir(), '.chatgpt'); return join(await homeDir(), '.chatgpt');
}; };
export const scriptRoot = async () => {
return join(await chatRoot(), 'scripts');
};
export const chatPromptPath = async (): Promise<string> => { export const chatPromptPath = async (): Promise<string> => {
return join(await chatRoot(), CHAT_PROMPT_JSON); return join(await chatRoot(), CHAT_PROMPT_JSON);
}; };

View File

@ -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;
}
}
}

View File

@ -8,7 +8,6 @@ import { fs, shell } from '@tauri-apps/api';
import useInit from '@/hooks/useInit'; import useInit from '@/hooks/useInit';
import SplitIcon from '@/icons/SplitIcon'; import SplitIcon from '@/icons/SplitIcon';
import { getPath } from '@/view/notes/config'; import { getPath } from '@/view/notes/config';
import './index.scss';
const modeMap: any = { const modeMap: any = {
0: 'split', 0: 'split',
@ -41,7 +40,7 @@ export default function Markdown() {
return ( return (
<> <>
<div className="md-task"> <div className="editor-task">
<Breadcrumb separator=""> <Breadcrumb separator="">
<Breadcrumb.Item onClick={() => history.go(-1)}> <Breadcrumb.Item onClick={() => history.go(-1)}>
<ArrowLeftOutlined /> <ArrowLeftOutlined />

View File

@ -67,5 +67,5 @@ const RenderPath = ({ row }: any) => {
}; };
export const getPath = async (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`);
}; };

View File

@ -7,7 +7,6 @@ export const syncColumns = () => [
title: '/{cmd}', title: '/{cmd}',
dataIndex: 'cmd', dataIndex: 'cmd',
fixed: 'left', fixed: 'left',
// width: 120,
key: 'cmd', key: 'cmd',
render: (_: string, row: Record<string, string>) => ( render: (_: string, row: Record<string, string>) => (
<Tag color="#2a2a2a">/{genCmd(row.act)}</Tag> <Tag color="#2a2a2a">/{genCmd(row.act)}</Tag>
@ -17,20 +16,17 @@ export const syncColumns = () => [
title: 'Act', title: 'Act',
dataIndex: 'act', dataIndex: 'act',
key: 'act', key: 'act',
// width: 200,
}, },
{ {
title: 'Tags', title: 'Tags',
dataIndex: 'tags', dataIndex: 'tags',
key: 'tags', key: 'tags',
// width: 150, render: () => <Tag>built-in</Tag>,
render: () => <Tag>chatgpt-prompts</Tag>,
}, },
{ {
title: 'Enable', title: 'Enable',
dataIndex: 'enable', dataIndex: 'enable',
key: 'enable', key: 'enable',
// width: 80,
render: (v: boolean = false, row: Record<string, any>, action: Record<string, any>) => ( render: (v: boolean = false, row: Record<string, any>, action: Record<string, any>) => (
<Switch checked={v} onChange={(v) => action.setRecord({ ...row, enable: v }, 'enable')} /> <Switch checked={v} onChange={(v) => action.setRecord({ ...row, enable: v }, 'enable')} />
), ),
@ -40,7 +36,6 @@ export const syncColumns = () => [
title: 'Prompt', title: 'Prompt',
dataIndex: 'prompt', dataIndex: 'prompt',
key: 'prompt', key: 'prompt',
// width: 300,
render: (v: string) => <span className="chat-prompts-val">{v}</span>, render: (v: string) => <span className="chat-prompts-val">{v}</span>,
}, },
]; ];

View File

@ -12,7 +12,8 @@ import { fmtDate, chatRoot } from '@/utils';
import { syncColumns } from './config'; import { syncColumns } from './config';
import './index.scss'; 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() { export default function SyncPrompts() {
const { rowSelection, selectedRowIDs } = useTableRowSelection(); const { rowSelection, selectedRowIDs } = useTableRowSelection();
@ -83,7 +84,7 @@ export default function SyncPrompts() {
</div> </div>
<div className="chat-table-tip"> <div className="chat-table-tip">
<div className="chat-sync-path"> <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" /> <FilePath label="CACHE" paths="cache_prompts/chatgpt_prompts.json" />
</div> </div>
{lastUpdated && ( {lastUpdated && (

View File

@ -7,7 +7,6 @@ export const syncColumns = () => [
title: '/{cmd}', title: '/{cmd}',
dataIndex: 'cmd', dataIndex: 'cmd',
fixed: 'left', fixed: 'left',
// width: 120,
key: 'cmd', key: 'cmd',
render: (_: string, row: Record<string, string>) => ( render: (_: string, row: Record<string, string>) => (
<Tag color="#2a2a2a">/{row.cmd ? row.cmd : genCmd(row.act)}</Tag> <Tag color="#2a2a2a">/{row.cmd ? row.cmd : genCmd(row.act)}</Tag>
@ -17,13 +16,11 @@ export const syncColumns = () => [
title: 'Act', title: 'Act',
dataIndex: 'act', dataIndex: 'act',
key: 'act', key: 'act',
// width: 200,
}, },
{ {
title: 'Tags', title: 'Tags',
dataIndex: 'tags', dataIndex: 'tags',
key: 'tags', key: 'tags',
// width: 150,
render: (v: string[]) => ( render: (v: string[]) => (
<span className="chat-prompts-tags"> <span className="chat-prompts-tags">
{v?.map((i) => ( {v?.map((i) => (
@ -36,7 +33,6 @@ export const syncColumns = () => [
title: 'Enable', title: 'Enable',
dataIndex: 'enable', dataIndex: 'enable',
key: 'enable', key: 'enable',
// width: 80,
render: (v: boolean = false, row: Record<string, any>, action: Record<string, any>) => ( render: (v: boolean = false, row: Record<string, any>, action: Record<string, any>) => (
<Switch checked={v} onChange={(v) => action.setRecord({ ...row, enable: v }, 'enable')} /> <Switch checked={v} onChange={(v) => action.setRecord({ ...row, enable: v }, 'enable')} />
), ),
@ -46,7 +42,6 @@ export const syncColumns = () => [
title: 'Prompt', title: 'Prompt',
dataIndex: 'prompt', dataIndex: 'prompt',
key: 'prompt', key: 'prompt',
// width: 300,
render: (v: string) => <span className="chat-prompts-val">{v}</span>, render: (v: string) => <span className="chat-prompts-val">{v}</span>,
}, },
]; ];

View File

@ -73,7 +73,7 @@ export default function SyncRecord() {
</div> </div>
<div className="chat-table-tip"> <div className="chat-table-tip">
<div className="chat-sync-path"> <div className="chat-sync-path">
<FilePath url={filePath} /> <FilePath label="URL" url={filePath} />
<FilePath label="CACHE" paths={`cache_prompts/${state?.id}.json`} /> <FilePath label="CACHE" paths={`cache_prompts/${state?.id}.json`} />
</div> </div>
{state?.last_updated && ( {state?.last_updated && (

View File

@ -45,12 +45,6 @@ const UserCustomForm: ForwardRefRenderFunction<FormProps, UserCustomFormProps> =
> >
<Input placeholder="Please enter the Act" {...DISABLE_AUTO_COMPLETE} /> <Input placeholder="Please enter the Act" {...DISABLE_AUTO_COMPLETE} />
</Form.Item> </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 <Form.Item
label="Prompt" label="Prompt"
name="prompt" name="prompt"
@ -58,6 +52,12 @@ const UserCustomForm: ForwardRefRenderFunction<FormProps, UserCustomFormProps> =
> >
<Input.TextArea rows={4} placeholder="Please enter a prompt" {...DISABLE_AUTO_COMPLETE} /> <Input.TextArea rows={4} placeholder="Please enter a prompt" {...DISABLE_AUTO_COMPLETE} />
</Form.Item> </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> </Form>
); );
}; };

View File

@ -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 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 { import useInit from '@/hooks/useInit';
value?: string; import { getPath } from './config';
interface ScriptEditorProps {
onChange?: (v: string) => void; onChange?: (v: string) => void;
mode?: string;
} }
const ScriptEditor: FC<MarkdownEditorProps> = ({ const ScriptEditor: FC<ScriptEditorProps> = ({ onChange }) => {
value = 'console.log', const [filePath, setFilePath] = useState('');
onChange, const [source, setSource] = useState('// write your script here\n');
mode = 'split', const location = useLocation();
}) => { const state = location?.state;
const [content, setContent] = useState(value);
useEffect(() => { useInit(async () => {
setContent(value); const file = await getPath(state);
onChange && onChange(value); setFilePath(file);
}, [value]); setSource(await fs.readTextFile(file));
});
const handleEdit = (e: any) => { const handleEdit = (e: any) => {
setContent(e); setSource(e);
onChange && onChange(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 ( return (
<div className="script-editor"> <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> </div>
); );
}; };

View File

@ -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>
</>
);
}

View File

@ -1,25 +1,37 @@
import { useState } from 'react'; import { useState } from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { Tag, Space, Popconfirm } from 'antd'; import { Tag, Space, Popconfirm } from 'antd';
import { ArrowRightOutlined } from '@ant-design/icons';
import { path, shell } from '@tauri-apps/api'; import { path, shell } from '@tauri-apps/api';
import useInit from '@/hooks/useInit'; import useInit from '@/hooks/useInit';
import { fmtDate, chatRoot } from '@/utils'; import { fmtDate, chatRoot } from '@/utils';
export const scriptColumns = () => [ export const scriptColumns = ({ scriptsMap }: any) => [
{ {
title: 'File Name', title: 'File Name',
dataIndex: 'name', dataIndex: 'name',
key: 'name', key: 'name',
width: 120, fixed: 'left',
render: (v: string) => <Tag>{v}</Tag>, width: 160,
render: (v: string) => <Tag color={v === 'main.js' ? 'green' : 'default'}>{v}</Tag>,
}, },
{ {
title: 'Version', title: 'Version',
dataIndex: 'version', width: 200,
key: 'version', render: (_: string, row: any) => {
width: 120, const next = scriptsMap?.[row.name]?.next_version;
render: (v: string) => <Tag>{v}</Tag>, 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', title: 'Path',
@ -28,6 +40,14 @@ export const scriptColumns = () => [
width: 200, width: 200,
render: (_: string, row: any) => <RenderPath row={row} />, 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', title: 'Created',
dataIndex: 'created', dataIndex: 'created',
@ -38,28 +58,41 @@ export const scriptColumns = () => [
{ {
title: 'Action', title: 'Action',
fixed: 'right', fixed: 'right',
width: 160, width: 100,
render: (_: any, row: any, actions: any) => { 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 ( return (
<Space> <Space>
<Link to={`/md/${row.id}`} state={row}> <Link
to={`/scripts/${row.id}`}
state={row}
style={{ color: !isExternal ? '#ff4d4f' : '' }}
>
Edit Edit
</Link> </Link>
<Popconfirm {!isExternal && next && next !== curr && (
title="Are you sure you want to synchronize? It will overwrite all previous modifications made to this file." <Popconfirm
onConfirm={() => actions.setRecord(row, 'sync')} placement="topLeft"
okText="Yes" title="Are you sure you want to synchronize? It will overwrite all previous modifications made to this file."
cancelText="No" onConfirm={() => actions.setRecord(row, 'sync')}
> okText="Yes"
<a>Sync</a> cancelText="No"
</Popconfirm> overlayStyle={{ width: 300 }}
>
<a>Sync</a>
</Popconfirm>
)}
</Space> </Space>
); );
}, },
}, },
]; ];
const RenderPath = ({ row }: any) => { export const RenderPath = ({ row }: any) => {
const [filePath, setFilePath] = useState(''); const [filePath, setFilePath] = useState('');
useInit(async () => { useInit(async () => {
setFilePath(await getPath(row)); setFilePath(await getPath(row));
@ -68,5 +101,5 @@ const RenderPath = ({ row }: any) => {
}; };
export const getPath = async (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}`);
}; };

View File

@ -1,10 +0,0 @@
.chatgpt-script {
.ant-collapse-header {
user-select: none;
-webkit-user-select: none;
}
}
.script-editor {
height: 300px;
}

View File

@ -1,17 +1,15 @@
import { useState } from 'react'; import { useEffect, useState } from 'react';
import { Table, Tag } from 'antd'; 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 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 { scriptColumns } from './config';
import ScriptHead, { type ScriptInfo } from './Head';
import ScriptEditor from './Editor';
import './index.scss';
// const { Panel } = Collapse;
const SCRIPTS = [ const SCRIPTS = [
'main.js',
'core.js', 'core.js',
'chat.js', 'chat.js',
'cmd.js', 'cmd.js',
@ -22,51 +20,54 @@ const SCRIPTS = [
]; ];
export default function 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) => { for (const script of scripts) {
setActiveKey(key); 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 = { useInit(handleInit);
onEdit(data: ScriptInfo) {
setActiveKey(data.name); useEffect(() => {
}, if (!opInfo.opType) return;
activeKey, (async () => {
}; if (opInfo.opType === 'sync') {
const isOk = await invoke('sync_scripts', { name: opInfo?.opRecord?.name });
if (isOk) {
await handleInit();
opInfo.resetRecord();
}
}
})();
}, [opInfo.opType]);
return ( return (
<div className="chatgpt-script"> <div className="chatgpt-script">
<Table <Table
rowKey="name"
scroll={{ x: 800 }}
columns={columns} columns={columns}
dataSource={SCRIPTS.map((i) => ({ name: i }))} dataSource={SCRIPTS.map((i) => ({ name: i }))}
{...TABLE_PAGINATION} {...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> </div>
); );
} }