From f822a56993dbcda379ee10a759324438c9b339ed Mon Sep 17 00:00:00 2001 From: lencx Date: Mon, 16 Jan 2023 21:00:56 +0800 Subject: [PATCH 01/30] chore: markdown --- src/components/Markdown/index.scss | 7 +++++ src/components/Markdown/index.tsx | 44 ++++++++++++++++++++++++++++++ src/view/notes/index.tsx | 28 ++----------------- 3 files changed, 54 insertions(+), 25 deletions(-) create mode 100644 src/components/Markdown/index.scss create mode 100644 src/components/Markdown/index.tsx diff --git a/src/components/Markdown/index.scss b/src/components/Markdown/index.scss new file mode 100644 index 0000000..a53c67c --- /dev/null +++ b/src/components/Markdown/index.scss @@ -0,0 +1,7 @@ +.markdown-body { + font-family: -apple-system,BlinkMacSystemFont,"Segoe UI","Noto Sans",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji"; + + pre, code { + font-family: monospace, monospace; + } +} \ No newline at end of file diff --git a/src/components/Markdown/index.tsx b/src/components/Markdown/index.tsx new file mode 100644 index 0000000..55bcddf --- /dev/null +++ b/src/components/Markdown/index.tsx @@ -0,0 +1,44 @@ +import { FC } from 'react'; +import ReactMarkdown from 'react-markdown'; +import SyntaxHighlighter from 'react-syntax-highlighter'; +import agate from 'react-syntax-highlighter/dist/esm/styles/hljs/agate'; + +import './index.scss'; + +interface MarkdownProps { + children: string; +} + +const Markdown: FC = ({ children }) => { + + return ( +
+ + ) : ( + + {children} + + ) + } + }} + /> +
+ ) +} + +export default Markdown; \ No newline at end of file diff --git a/src/view/notes/index.tsx b/src/view/notes/index.tsx index 38f0ab0..5b29c6e 100644 --- a/src/view/notes/index.tsx +++ b/src/view/notes/index.tsx @@ -1,14 +1,12 @@ import { useEffect, useState } from 'react'; import { Table, Modal, Popconfirm, Button, message } from 'antd'; import { invoke, path, shell, fs } from '@tauri-apps/api'; -import ReactMarkdown from 'react-markdown'; -import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; -import { a11yDark } from 'react-syntax-highlighter/dist/esm/styles/prism'; import useInit from '@/hooks/useInit'; import useJson from '@/hooks/useJson'; import useData from '@/hooks/useData'; import useColumns from '@/hooks/useColumns'; +import Markdown from '@/components/Markdown'; import { useTableRowSelection, TABLE_PAGINATION } from '@/hooks/useTable'; import { chatRoot, CHAT_NOTES_JSON } from '@/utils'; import { notesColumns } from './config'; @@ -132,29 +130,9 @@ export default function Notes() { onCancel={handleCancel} footer={false} destroyOnClose + width={600} > - - ) : ( - - {children} - - ) - } - }} - /> + ) From 240c88220d553d705c0070509caa425d740cbcb2 Mon Sep 17 00:00:00 2001 From: lencx Date: Mon, 16 Jan 2023 21:57:00 +0800 Subject: [PATCH 02/30] chore: deps --- src-tauri/Cargo.toml | 11 +++-------- src-tauri/src/app/window.rs | 2 +- src-tauri/src/main.rs | 8 ++++---- 3 files changed, 8 insertions(+), 13 deletions(-) diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index a7818a9..3d2132f 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -26,15 +26,10 @@ wry = "0.24.1" dark-light = "1.0.0" serde = { version = "1.0", features = ["derive"] } tokio = { version = "1.23.0", features = ["macros"] } -tauri-plugin-positioner = { version = "1.0.4", features = ["system-tray"] } tauri = { version = "1.2.3", features = ["api-all", "devtools", "global-shortcut", "system-tray", "updater"] } -[dependencies.tauri-plugin-log] -git = "https://github.com/lencx/tauri-plugin-log" -branch = "dev" -features = ["colored"] -[dependencies.tauri-plugin-autostart] -git = "https://github.com/lencx/tauri-plugin-autostart" -branch = "dev" +tauri-plugin-positioner = { version = "1.0.4", features = ["system-tray"] } +tauri-plugin-log = { git = "https://github.com/lencx/tauri-plugins-workspace", branch = "dev", features = ["colored"] } +tauri-plugin-autostart = { git = "https://github.com/lencx/tauri-plugins-workspace", branch = "dev" } # sqlx = { version = "0.6.2", features = ["runtime-tokio-rustls", "sqlite"] } diff --git a/src-tauri/src/app/window.rs b/src-tauri/src/app/window.rs index d1dcf53..d614214 100644 --- a/src-tauri/src/app/window.rs +++ b/src-tauri/src/app/window.rs @@ -94,7 +94,7 @@ pub fn control_window(handle: &tauri::AppHandle) { .title("Control Center") .resizable(true) .fullscreen(false) - .inner_size(800.0, 600.0) + .inner_size(1000.0, 700.0) .min_inner_size(800.0, 600.0) .build() .unwrap(); diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index b41eee9..bed964b 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -13,7 +13,7 @@ use tauri::api::path; use tauri_plugin_autostart::MacosLauncher; use tauri_plugin_log::{ fern::colors::{Color, ColoredLevelConfig}, - LogTarget, LoggerBuilder, + LogTarget, }; #[tokio::main] @@ -38,9 +38,7 @@ async fn main() { let mut builder = tauri::Builder::default() // https://github.com/tauri-apps/tauri/pull/2736 .plugin( - LoggerBuilder::new() - .level(log::LevelFilter::Debug) - .with_colors(colors) + tauri_plugin_log::Builder::default() .targets([ // LogTarget::LogDir, // LOG PATH: ~/.chatgpt/ChatGPT.log @@ -48,6 +46,8 @@ async fn main() { LogTarget::Stdout, LogTarget::Webview, ]) + .level(log::LevelFilter::Debug) + .with_colors(colors) .build(), ) .plugin(tauri_plugin_positioner::init()) From f38d683f4eac7d500c80602bcd0b2578336402c7 Mon Sep 17 00:00:00 2001 From: lencx Date: Tue, 17 Jan 2023 22:52:15 +0800 Subject: [PATCH 03/30] chore: optim --- src-tauri/src/app/setup.rs | 36 ++++++++---------------------------- 1 file changed, 8 insertions(+), 28 deletions(-) diff --git a/src-tauri/src/app/setup.rs b/src-tauri/src/app/setup.rs index 1dcbe76..9c2d5cd 100644 --- a/src-tauri/src/app/setup.rs +++ b/src-tauri/src/app/setup.rs @@ -50,13 +50,17 @@ pub fn init(app: &mut App) -> std::result::Result<(), Box } else { let app = app.handle(); tauri::async_runtime::spawn(async move { - #[cfg(target_os = "macos")] - WindowBuilder::new(&app, "core", WindowUrl::App(url.into())) + let mut main_win = WindowBuilder::new(&app, "core", WindowUrl::App(url.into())) .title("ChatGPT") .resizable(true) .fullscreen(false) - .inner_size(800.0, 600.0) - .hidden_title(true) + .inner_size(800.0, 600.0); + + if cfg!(target_os = "macos") { + main_win = main_win.hidden_title(true); + } + + main_win .theme(theme) .always_on_top(chat_conf.stay_on_top) .title_bar_style(ChatConfJson::titlebar()) @@ -75,30 +79,6 @@ pub fn init(app: &mut App) -> std::result::Result<(), Box .user_agent(&chat_conf.ua_window) .build() .unwrap(); - - #[cfg(not(target_os = "macos"))] - WindowBuilder::new(&app, "core", WindowUrl::App(url.into())) - .title("ChatGPT") - .resizable(true) - .fullscreen(false) - .inner_size(800.0, 600.0) - .theme(theme) - .always_on_top(chat_conf.stay_on_top) - .initialization_script(&utils::user_script()) - .initialization_script(include_str!("../vendors/floating-ui-core.js")) - .initialization_script(include_str!("../vendors/floating-ui-dom.js")) - .initialization_script(include_str!("../vendors/html2canvas.js")) - .initialization_script(include_str!("../vendors/jspdf.js")) - .initialization_script(include_str!("../vendors/turndown.js")) - .initialization_script(include_str!("../vendors/turndown-plugin-gfm.js")) - .initialization_script(include_str!("../scripts/core.js")) - .initialization_script(include_str!("../scripts/popup.core.js")) - .initialization_script(include_str!("../scripts/export.js")) - .initialization_script(include_str!("../scripts/markdown.export.js")) - .initialization_script(include_str!("../scripts/cmd.js")) - .user_agent(&chat_conf.ua_window) - .build() - .unwrap(); }); } From a7d12bafc03aef1549a785919ec76239814efdfd Mon Sep 17 00:00:00 2001 From: lencx Date: Wed, 18 Jan 2023 00:03:45 +0800 Subject: [PATCH 04/30] chore: path --- package.json | 3 +++ src/components/FilePath/index.tsx | 36 ++++++++++++++++++++++++++++ src/components/Markdown/Editor.tsx | 25 +++++++++++++++++++ src/components/Markdown/index.scss | 14 ++++++++++- src/main.scss | 1 + src/routes.tsx | 6 +++++ src/utils.ts | 1 + src/view/General.tsx | 14 ++++------- src/view/download/index.tsx | 16 +++---------- src/view/markdown/index.tsx | 32 +++++++++++++++++++++++++ src/view/model/SyncCustom/index.tsx | 2 +- src/view/model/SyncPrompts/index.tsx | 7 +++--- src/view/model/SyncRecord/index.tsx | 11 +++++---- src/view/model/UserCustom/index.tsx | 10 ++++---- src/view/notes/config.tsx | 3 ++- src/view/notes/index.tsx | 19 +++------------ 16 files changed, 144 insertions(+), 56 deletions(-) create mode 100644 src/components/FilePath/index.tsx create mode 100644 src/components/Markdown/Editor.tsx create mode 100644 src/view/markdown/index.tsx diff --git a/package.json b/package.json index 6f8ca26..d1f8611 100644 --- a/package.json +++ b/package.json @@ -34,13 +34,16 @@ }, "dependencies": { "@ant-design/icons": "^4.8.0", + "@monaco-editor/react": "^4.4.6", "@tauri-apps/api": "^1.2.0", "antd": "^5.1.0", + "clsx": "^1.2.1", "dayjs": "^1.11.7", "lodash": "^4.17.21", "react": "^18.2.0", "react-dom": "^18.2.0", "react-markdown": "^8.0.4", + "react-resizable-panels": "^0.0.33", "react-router-dom": "^6.4.5", "react-syntax-highlighter": "^15.5.0", "uuid": "^9.0.0" diff --git a/src/components/FilePath/index.tsx b/src/components/FilePath/index.tsx new file mode 100644 index 0000000..8976710 --- /dev/null +++ b/src/components/FilePath/index.tsx @@ -0,0 +1,36 @@ +import { FC, useEffect, useState } from 'react'; +import clsx from 'clsx'; +import { path, shell } from '@tauri-apps/api'; + +import { chatRoot } from '@/utils'; + +interface FilePathProps { + paths?: string; + label?: string; + className?: string; + content?: string; + url?: string; +} + +const FilePath: FC = ({ className, label = 'PATH', paths = '', url, content }) => { + const [filePath, setPath] = useState(''); + + useEffect(() => { + if (!path && !url) return; + (async () => { + if (url) { + setPath(url); + return; + } + setPath(await path.join(await chatRoot(), ...paths.split('/').filter(i => !!i))); + })() + }, [url, paths]) + + return ( + + ); +} + +export default FilePath; \ No newline at end of file diff --git a/src/components/Markdown/Editor.tsx b/src/components/Markdown/Editor.tsx new file mode 100644 index 0000000..2db5368 --- /dev/null +++ b/src/components/Markdown/Editor.tsx @@ -0,0 +1,25 @@ +import Editor from "@monaco-editor/react"; +import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels'; + +import './index.scss'; + +const MarkdownEditor = () => { + return ( +
+ + + + + + +
1284
+
+
+
+ ) +}; + +export default MarkdownEditor; \ No newline at end of file diff --git a/src/components/Markdown/index.scss b/src/components/Markdown/index.scss index a53c67c..1c5bcb0 100644 --- a/src/components/Markdown/index.scss +++ b/src/components/Markdown/index.scss @@ -4,4 +4,16 @@ pre, code { font-family: monospace, monospace; } -} \ No newline at end of file +} + +.resize-handle { + width: 0.25rem; + transition: 250ms linear background-color; + background-color: #7f8082; + outline: none; + + &:hover, + &[data-resize-handle-active] { + background-color: #5194eb; + } +} diff --git a/src/main.scss b/src/main.scss index a2eabaa..d94bad3 100644 --- a/src/main.scss +++ b/src/main.scss @@ -19,6 +19,7 @@ html, body { margin: 0; } +.ant-table-selection-col, .ant-table-selection-column { width: 50px !important; min-width: 50px !important; diff --git a/src/routes.tsx b/src/routes.tsx index 41527cb..187c2b6 100644 --- a/src/routes.tsx +++ b/src/routes.tsx @@ -17,6 +17,7 @@ import SyncCustom from '@/view/model/SyncCustom'; import SyncRecord from '@/view/model/SyncRecord'; import Download from '@/view/download'; import Notes from '@/view/notes'; +import Markdown from '@/view/markdown'; export type ChatRouteMetaObject = { label: string; @@ -48,6 +49,11 @@ export const routes: Array = [ icon: , }, }, + { + path: '/md/:id', + element: , + hideMenu: true, + }, { path: '/model', meta: { diff --git a/src/utils.ts b/src/utils.ts index b90a7bd..ba74daf 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -2,6 +2,7 @@ import { readTextFile, writeTextFile, exists, createDir } from '@tauri-apps/api/ import { homeDir, join, dirname } from '@tauri-apps/api/path'; import dayjs from 'dayjs'; +export const CHAT_CONF_JSON = 'chat.conf.json'; export const CHAT_MODEL_JSON = 'chat.model.json'; export const CHAT_MODEL_CMD_JSON = 'chat.model.cmd.json'; export const CHAT_DOWNLOAD_JSON = 'chat.download.json'; diff --git a/src/view/General.tsx b/src/view/General.tsx index 6dfbdab..7e709ad 100644 --- a/src/view/General.tsx +++ b/src/view/General.tsx @@ -1,14 +1,15 @@ import { useEffect, useState } from 'react'; import { Form, Radio, Switch, Input, Button, Space, message, Tooltip } from 'antd'; import { QuestionCircleOutlined } from '@ant-design/icons'; -import { invoke, shell, path } from '@tauri-apps/api'; +import { invoke } from '@tauri-apps/api'; import { platform } from '@tauri-apps/api/os'; import { ask } from '@tauri-apps/api/dialog'; import { relaunch } from '@tauri-apps/api/process'; import { clone, omit, isEqual } from 'lodash'; import useInit from '@/hooks/useInit'; -import { DISABLE_AUTO_COMPLETE, chatRoot } from '@/utils'; +import FilePath from '@/components/FilePath'; +import { DISABLE_AUTO_COMPLETE, CHAT_CONF_JSON } from '@/utils'; const AutoUpdateLabel = () => { return ( @@ -68,13 +69,10 @@ const GlobalShortcutLabel = () => { export default function General() { const [form] = Form.useForm(); - const [jsonPath, setJsonPath] = useState(''); const [platformInfo, setPlatform] = useState(''); const [chatConf, setChatConf] = useState(null); useInit(async () => { - setJsonPath(await path.join(await chatRoot(), 'chat.conf.json')); - setPlatform(await platform()); const chatData = await invoke('get_chat_conf'); setChatConf(chatData); @@ -117,11 +115,7 @@ export default function General() { return ( <> - +
(CHAT_DOWNLOAD_JSON); const selectedItems = rowSelection.selectedRowKeys || []; - useInit(async () => { - const file = await path.join(await chatRoot(), CHAT_DOWNLOAD_JSON); - setDownloadPath(file); - }); - useEffect(() => { if (!json || json.length <= 0) return; opInit(json); @@ -118,11 +112,7 @@ export default function Download() { )} - + { + setFilePath(await getPath(state)); + }) + + return ( + <> + + + + + + {filePath} + + + + + ); +} \ No newline at end of file diff --git a/src/view/model/SyncCustom/index.tsx b/src/view/model/SyncCustom/index.tsx index 936283c..d20b045 100644 --- a/src/view/model/SyncCustom/index.tsx +++ b/src/view/model/SyncCustom/index.tsx @@ -3,9 +3,9 @@ import { Table, Modal, Button, message } from 'antd'; import { invoke, path, fs } from '@tauri-apps/api'; import useData from '@/hooks/useData'; -import useChatModel, { useCacheModel } from '@/hooks/useChatModel'; import useColumns from '@/hooks/useColumns'; import { TABLE_PAGINATION } from '@/hooks/useTable'; +import useChatModel, { useCacheModel } from '@/hooks/useChatModel'; import { CHAT_MODEL_JSON, chatRoot, readJSON, genCmd } from '@/utils'; import { syncColumns, getPath } from './config'; import SyncForm from './Form'; diff --git a/src/view/model/SyncPrompts/index.tsx b/src/view/model/SyncPrompts/index.tsx index 7db4d2e..8a26a84 100644 --- a/src/view/model/SyncPrompts/index.tsx +++ b/src/view/model/SyncPrompts/index.tsx @@ -1,10 +1,11 @@ import { useEffect, useState } from 'react'; import { Table, Button, Popconfirm } from 'antd'; -import { invoke, path, shell } from '@tauri-apps/api'; +import { invoke, path } from '@tauri-apps/api'; import useInit from '@/hooks/useInit'; import useData from '@/hooks/useData'; import useColumns from '@/hooks/useColumns'; +import FilePath from '@/components/FilePath'; import useChatModel, { useCacheModel } from '@/hooks/useChatModel'; import { useTableRowSelection, TABLE_PAGINATION } from '@/hooks/useTable'; import { fmtDate, chatRoot } from '@/utils'; @@ -80,8 +81,8 @@ export default function SyncPrompts() { diff --git a/src/view/model/SyncRecord/index.tsx b/src/view/model/SyncRecord/index.tsx index 9f22759..b74344a 100644 --- a/src/view/model/SyncRecord/index.tsx +++ b/src/view/model/SyncRecord/index.tsx @@ -2,14 +2,15 @@ import { useEffect, useState } from 'react'; import { useLocation } from 'react-router-dom'; import { ArrowLeftOutlined } from '@ant-design/icons'; import { Table, Button } from 'antd'; -import { shell, path } from '@tauri-apps/api'; +import { path } from '@tauri-apps/api'; -import useColumns from '@/hooks/useColumns'; import useData from '@/hooks/useData'; +import useColumns from '@/hooks/useColumns'; +import FilePath from '@/components/FilePath'; import { useCacheModel } from '@/hooks/useChatModel'; import { useTableRowSelection, TABLE_PAGINATION } from '@/hooks/useTable'; -import { fmtDate, chatRoot } from '@/utils'; import { getPath } from '@/view/model/SyncCustom/config'; +import { fmtDate, chatRoot } from '@/utils'; import { syncColumns } from './config'; import useInit from '@/hooks/useInit'; @@ -66,8 +67,8 @@ export default function SyncRecord() {
{state?.last_updated && Last updated on {fmtDate(state?.last_updated)}}
diff --git a/src/view/model/UserCustom/index.tsx b/src/view/model/UserCustom/index.tsx index 985fe41..5c85e84 100644 --- a/src/view/model/UserCustom/index.tsx +++ b/src/view/model/UserCustom/index.tsx @@ -1,11 +1,12 @@ import { useState, useRef, useEffect } from 'react'; import { Table, Button, Modal, message } from 'antd'; -import { shell, path } from '@tauri-apps/api'; +import { path } from '@tauri-apps/api'; import useInit from '@/hooks/useInit'; import useData from '@/hooks/useData'; -import useChatModel, { useCacheModel } from '@/hooks/useChatModel'; import useColumns from '@/hooks/useColumns'; +import FilePath from '@/components/FilePath'; +import useChatModel, { useCacheModel } from '@/hooks/useChatModel'; import { useTableRowSelection, TABLE_PAGINATION } from '@/hooks/useTable'; import { chatRoot, fmtDate } from '@/utils'; import { modelColumns } from './config'; @@ -108,11 +109,8 @@ export default function LanguageModel() { )} - {/*
PATH: {modelPath}
*/}
- + {lastUpdated && Last updated on {fmtDate(lastUpdated)}}
[ return ( actions.setRecord(row, 'preview')}>Preview - actions.setRecord(row, 'edit')}>Edit + Edit actions.setRecord(row, 'delete')} diff --git a/src/view/notes/index.tsx b/src/view/notes/index.tsx index 5b29c6e..1ce675a 100644 --- a/src/view/notes/index.tsx +++ b/src/view/notes/index.tsx @@ -1,18 +1,17 @@ import { useEffect, useState } from 'react'; import { Table, Modal, Popconfirm, Button, message } from 'antd'; -import { invoke, path, shell, fs } from '@tauri-apps/api'; +import { invoke, path, fs } from '@tauri-apps/api'; -import useInit from '@/hooks/useInit'; import useJson from '@/hooks/useJson'; import useData from '@/hooks/useData'; import useColumns from '@/hooks/useColumns'; import Markdown from '@/components/Markdown'; +import FilePath from '@/components/FilePath'; import { useTableRowSelection, TABLE_PAGINATION } from '@/hooks/useTable'; import { chatRoot, CHAT_NOTES_JSON } from '@/utils'; import { notesColumns } from './config'; export default function Notes() { - const [notesPath, setNotesPath] = useState(''); const [source, setSource] = useState(''); const [isVisible, setVisible] = useState(false); const { opData, opInit, opReplace, opSafeKey } = useData([]); @@ -21,11 +20,6 @@ export default function Notes() { const { json, refreshJson, updateJson } = useJson(CHAT_NOTES_JSON); const selectedItems = rowSelection.selectedRowKeys || []; - useInit(async () => { - const file = await path.join(await chatRoot(), CHAT_NOTES_JSON); - setNotesPath(file); - }); - useEffect(() => { if (!json || json.length <= 0) return; opInit(json); @@ -42,9 +36,6 @@ export default function Notes() { setVisible(true); return; } - if (opInfo.opType === 'edit') { - alert('TODO'); - } if (opInfo.opType === 'delete') { await fs.removeFile(file); await handleRefresh(); @@ -111,11 +102,7 @@ export default function Notes() { )} - +
Date: Thu, 19 Jan 2023 00:13:10 +0800 Subject: [PATCH 05/30] chore: markdown --- package.json | 1 + src/components/Markdown/Editor.tsx | 28 ++++++++++++--- src/components/Markdown/index.scss | 13 +++++++ src/components/Markdown/index.tsx | 58 ++++++++++++++++-------------- src/main.scss | 5 ++- src/view/markdown/index.scss | 15 ++++++++ src/view/markdown/index.tsx | 31 ++++++++++------ 7 files changed, 108 insertions(+), 43 deletions(-) create mode 100644 src/view/markdown/index.scss diff --git a/package.json b/package.json index d1f8611..de5c24d 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "react-resizable-panels": "^0.0.33", "react-router-dom": "^6.4.5", "react-syntax-highlighter": "^15.5.0", + "remark-gfm": "^3.0.1", "uuid": "^9.0.0" }, "devDependencies": { diff --git a/src/components/Markdown/Editor.tsx b/src/components/Markdown/Editor.tsx index 2db5368..cca249d 100644 --- a/src/components/Markdown/Editor.tsx +++ b/src/components/Markdown/Editor.tsx @@ -1,21 +1,41 @@ +import { FC, useEffect, useState } from 'react'; import Editor from "@monaco-editor/react"; import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels'; +import Markdown from '@/components/Markdown'; import './index.scss'; -const MarkdownEditor = () => { +interface MarkdownEditorProps { + value?: string; + onChange?: (v: string) => void; +} + +const MarkdownEditor: FC = ({ value = '', onChange }) => { + const [content, setContent] = useState(value); + + useEffect(() => { + setContent(value); + onChange && onChange(value); + }, [value]) + + const handleEdit = (e: any) => { + setContent(e); + onChange && onChange(e); + } + return ( -
+
-
1284
+ {content}
diff --git a/src/components/Markdown/index.scss b/src/components/Markdown/index.scss index 1c5bcb0..033bd36 100644 --- a/src/components/Markdown/index.scss +++ b/src/components/Markdown/index.scss @@ -1,11 +1,24 @@ .markdown-body { + height: 100%; + overflow: auto; font-family: -apple-system,BlinkMacSystemFont,"Segoe UI","Noto Sans",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji"; + + &.edit-preview { + padding: 10px; + font-size: 14px; + } + pre, code { font-family: monospace, monospace; } } +.md-main { + height: calc(100vh - 130px); + overflow: hidden; +} + .resize-handle { width: 0.25rem; transition: 250ms linear background-color; diff --git a/src/components/Markdown/index.tsx b/src/components/Markdown/index.tsx index 55bcddf..a548cc6 100644 --- a/src/components/Markdown/index.tsx +++ b/src/components/Markdown/index.tsx @@ -1,5 +1,7 @@ import { FC } from 'react'; +import clsx from 'clsx'; import ReactMarkdown from 'react-markdown'; +import remarkGfm from 'remark-gfm'; import SyntaxHighlighter from 'react-syntax-highlighter'; import agate from 'react-syntax-highlighter/dist/esm/styles/hljs/agate'; @@ -7,36 +9,40 @@ import './index.scss'; interface MarkdownProps { children: string; + className?: string; } -const Markdown: FC = ({ children }) => { +const Markdown: FC = ({ children, className }) => { return ( -
- - ) : ( - - {children} - - ) - } - }} - /> +
+
+ + ) : ( + + {children} + + ) + } + }} + /> +
) } diff --git a/src/main.scss b/src/main.scss index d94bad3..cbaf8cc 100644 --- a/src/main.scss +++ b/src/main.scss @@ -69,12 +69,11 @@ html, body { } } -.chat-file-path, -.chat-sync-path { +.chat-file-path { font-size: 12px; font-weight: 500; color: #888; - margin-bottom: 5px; + margin-bottom: 3px; line-height: 16px; > div { diff --git a/src/view/markdown/index.scss b/src/view/markdown/index.scss new file mode 100644 index 0000000..3b61798 --- /dev/null +++ b/src/view/markdown/index.scss @@ -0,0 +1,15 @@ + +.md-task { + margin-bottom: 5px; + + .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; + } + } +} \ No newline at end of file diff --git a/src/view/markdown/index.tsx b/src/view/markdown/index.tsx index e80f2b4..ed1a165 100644 --- a/src/view/markdown/index.tsx +++ b/src/view/markdown/index.tsx @@ -3,30 +3,41 @@ import { useLocation } from 'react-router-dom'; import { Breadcrumb } from 'antd'; import { ArrowLeftOutlined } from '@ant-design/icons'; import MarkdownEditor from '@/components/Markdown/Editor'; +import { fs, shell } from '@tauri-apps/api'; import useInit from '@/hooks/useInit'; import { getPath } from '@/view/notes/config'; +import './index.scss'; export default function Markdown() { const [filePath, setFilePath] = useState(''); + const [source, setSource] = useState(''); const location = useLocation(); const state = location?.state; useInit(async () => { - setFilePath(await getPath(state)); + const file = await getPath(state); + setFilePath(file); + setSource(await fs.readTextFile(file)) }) + const handleChange = async (v: string) => { + await fs.writeTextFile(filePath, v); + }; + return ( <> - - - - - - {filePath} - - - +
+ + history.go(-1)}> + + + shell.open(filePath)}> + {filePath} + + +
+ ); } \ No newline at end of file From 5f1c33d7506e3927d99bd8ae0c8d48d13b82ab5e Mon Sep 17 00:00:00 2001 From: lencx Date: Fri, 20 Jan 2023 21:28:57 +0800 Subject: [PATCH 06/30] chore: awesome --- src/components/Markdown/Editor.tsx | 31 ++++--- src/components/Tags/index.tsx | 8 +- src/icons/SplitIcon.tsx | 17 ++++ src/layout/index.tsx | 2 +- src/main.scss | 5 ++ src/routes.tsx | 18 +++- src/utils.ts | 2 + src/view/awesome/Form.tsx | 63 ++++++++++++++ src/view/awesome/config.tsx | 68 +++++++++++++++ src/view/awesome/index.tsx | 125 ++++++++++++++++++++++++++++ src/view/download/index.tsx | 2 +- src/view/markdown/index.scss | 2 + src/view/markdown/index.tsx | 19 ++++- src/view/model/SyncCustom/Form.tsx | 6 +- src/view/model/SyncCustom/index.tsx | 15 ++-- src/view/model/UserCustom/Form.tsx | 12 +-- src/view/model/UserCustom/index.tsx | 8 -- src/view/notes/index.tsx | 2 +- 18 files changed, 359 insertions(+), 46 deletions(-) create mode 100644 src/icons/SplitIcon.tsx create mode 100644 src/view/awesome/Form.tsx create mode 100644 src/view/awesome/config.tsx create mode 100644 src/view/awesome/index.tsx diff --git a/src/components/Markdown/Editor.tsx b/src/components/Markdown/Editor.tsx index cca249d..22fe339 100644 --- a/src/components/Markdown/Editor.tsx +++ b/src/components/Markdown/Editor.tsx @@ -8,9 +8,10 @@ import './index.scss'; interface MarkdownEditorProps { value?: string; onChange?: (v: string) => void; + mode?: string; } -const MarkdownEditor: FC = ({ value = '', onChange }) => { +const MarkdownEditor: FC = ({ value = '', onChange, mode = 'split' }) => { const [content, setContent] = useState(value); useEffect(() => { @@ -23,20 +24,26 @@ const MarkdownEditor: FC = ({ value = '', onChange }) => { onChange && onChange(e); } + const isSplit = mode === 'split'; + return (
- - - - - - {content} - + {['md', 'split'].includes(mode) && ( + + + + )} + {isSplit && } + {['doc', 'split'].includes(mode) && ( + + {content} + + )}
) diff --git a/src/components/Tags/index.tsx b/src/components/Tags/index.tsx index b88fa88..dd349e4 100644 --- a/src/components/Tags/index.tsx +++ b/src/components/Tags/index.tsx @@ -8,9 +8,11 @@ import { DISABLE_AUTO_COMPLETE } from '@/utils'; interface TagsProps { value?: string[]; onChange?: (v: string[]) => void; + addTxt?: string; + max?: number; } -const Tags: FC = ({ value = [], onChange }) => { +const Tags: FC = ({ max = 99, value = [], onChange, addTxt = 'New Tag' }) => { const [tags, setTags] = useState(value); const [inputVisible, setInputVisible] = useState(false); const [inputValue, setInputValue] = useState(''); @@ -86,9 +88,9 @@ const Tags: FC = ({ value = [], onChange }) => { {...DISABLE_AUTO_COMPLETE} /> )} - {!inputVisible && ( + {!inputVisible && tags.length < max && ( - New Tag + {addTxt} )} diff --git a/src/icons/SplitIcon.tsx b/src/icons/SplitIcon.tsx new file mode 100644 index 0000000..4192674 --- /dev/null +++ b/src/icons/SplitIcon.tsx @@ -0,0 +1,17 @@ +import Icon from "@ant-design/icons"; +import type { CustomIconComponentProps } from '@ant-design/icons/lib/components/Icon'; + +interface IconProps { + onClick: () => void; +} + +export default function SplitIcon(props: Partial) { + return ( + + + + )} + /> +} diff --git a/src/layout/index.tsx b/src/layout/index.tsx index d5397f9..e100e9b 100644 --- a/src/layout/index.tsx +++ b/src/layout/index.tsx @@ -66,7 +66,7 @@ export default function ChatLayout() { theme={ appInfo.appTheme === "dark" ? "dark" : "light" } inlineIndent={12} items={menuItems} - defaultOpenKeys={['/model']} + // defaultOpenKeys={['/model']} onClick={(i) => go(i.key)} /> diff --git a/src/main.scss b/src/main.scss index cbaf8cc..8bf3c9f 100644 --- a/src/main.scss +++ b/src/main.scss @@ -57,6 +57,7 @@ html, body { margin-bottom: 5px; } +.chat-tags, .chat-prompts-tags { .ant-tag { margin: 2px; @@ -95,4 +96,8 @@ html, body { cursor: pointer; text-decoration: underline; } +} + +.chatico { + cursor: pointer; } \ No newline at end of file diff --git a/src/routes.tsx b/src/routes.tsx index 187c2b6..5e17488 100644 --- a/src/routes.tsx +++ b/src/routes.tsx @@ -7,10 +7,12 @@ import { UserOutlined, DownloadOutlined, FormOutlined, + GlobalOutlined, } from '@ant-design/icons'; import type { MenuProps } from 'antd'; -import General from '@view/General'; +import General from '@/view/General'; +import Awesome from '@/view/awesome'; import UserCustom from '@/view/model/UserCustom'; import SyncPrompts from '@/view/model/SyncPrompts'; import SyncCustom from '@/view/model/SyncCustom'; @@ -35,10 +37,10 @@ type ChatRouteObject = { export const routes: Array = [ { path: '/', - element: , + element: , meta: { - label: 'General', - icon: , + label: 'Awesome', + icon: , }, }, { @@ -101,6 +103,14 @@ export const routes: Array = [ icon: , }, }, + { + path: '/general', + element: , + meta: { + label: 'General', + icon: , + }, + }, ]; type MenuItem = Required['items'][number]; diff --git a/src/utils.ts b/src/utils.ts index ba74daf..c65f117 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -6,9 +6,11 @@ export const CHAT_CONF_JSON = 'chat.conf.json'; export const CHAT_MODEL_JSON = 'chat.model.json'; export const CHAT_MODEL_CMD_JSON = 'chat.model.cmd.json'; export const CHAT_DOWNLOAD_JSON = 'chat.download.json'; +export const CHAT_AWESOME_JSON = 'chat.awesome.json'; export const CHAT_NOTES_JSON = 'chat.notes.json'; export const CHAT_PROMPTS_CSV = 'chat.prompts.csv'; export const GITHUB_PROMPTS_CSV_URL = 'https://raw.githubusercontent.com/f/awesome-chatgpt-prompts/main/prompts.csv'; + export const DISABLE_AUTO_COMPLETE = { autoCapitalize: 'off', autoComplete: 'off', diff --git a/src/view/awesome/Form.tsx b/src/view/awesome/Form.tsx new file mode 100644 index 0000000..aa55668 --- /dev/null +++ b/src/view/awesome/Form.tsx @@ -0,0 +1,63 @@ +import { useEffect, ForwardRefRenderFunction, useImperativeHandle, forwardRef } from 'react'; +import { Form, Input, Switch } from 'antd'; +import type { FormProps } from 'antd'; + +import Tags from '@comps/Tags'; +import { DISABLE_AUTO_COMPLETE } from '@/utils'; + +interface AwesomeFormProps { + record?: Record | null; +} + +const initFormValue = { + title: '', + url: '', + enable: true, + tags: [], + category: '', +}; + +const AwesomeForm: ForwardRefRenderFunction = ({ record }, ref) => { + const [form] = Form.useForm(); + useImperativeHandle(ref, () => ({ form })); + + useEffect(() => { + if (record) { + form.setFieldsValue(record); + } + }, [record]); + + return ( + + + + + + + + + + + + + + + + + + ) +} + +export default forwardRef(AwesomeForm); diff --git a/src/view/awesome/config.tsx b/src/view/awesome/config.tsx new file mode 100644 index 0000000..208c752 --- /dev/null +++ b/src/view/awesome/config.tsx @@ -0,0 +1,68 @@ +import { Tag, Space, Popconfirm, Switch } from 'antd'; + +export const awesomeColumns = () => [ + { + title: 'Title', + dataIndex: 'title', + fixed: 'left', + key: 'title', + width: 160, + }, + { + title: 'URL', + dataIndex: 'url', + key: 'url', + width: 120, + }, + // { + // title: 'Icon', + // dataIndex: 'icon', + // key: 'icon', + // width: 120, + // }, + { + title: 'Enable', + dataIndex: 'enable', + key: 'enable', + width: 80, + render: (v: boolean = true, row: Record, action: Record) => ( + action.setRecord({ ...row, enable: v }, 'enable')} /> + ), + }, + { + title: 'Category', + dataIndex: 'category', + key: 'category', + width: 200, + render: (v: string) => {v} + }, + { + title: 'Tags', + dataIndex: 'tags', + key: 'tags', + width: 150, + render: (v: string[]) => ( + {v?.map(i => {i})} + ), + }, + { + title: 'Action', + fixed: 'right', + width: 150, + render: (_: any, row: any, actions: any) => { + return ( + + actions.setRecord(row, 'edit')}>Edit + actions.setRecord(row, 'delete')} + okText="Yes" + cancelText="No" + > + Delete + + + ) + } + } +]; diff --git a/src/view/awesome/index.tsx b/src/view/awesome/index.tsx new file mode 100644 index 0000000..1660d98 --- /dev/null +++ b/src/view/awesome/index.tsx @@ -0,0 +1,125 @@ +import { useRef, useEffect, useState } from 'react'; +import { Table, Modal, Popconfirm, Button, message } from 'antd'; + +import useJson from '@/hooks/useJson'; +import useData from '@/hooks/useData'; +import useColumns from '@/hooks/useColumns'; +import FilePath from '@/components/FilePath'; +import { CHAT_AWESOME_JSON } from '@/utils'; +import { useTableRowSelection, TABLE_PAGINATION } from '@/hooks/useTable'; +import { awesomeColumns } from './config'; +import AwesomeForm from './Form'; + +export default function Awesome() { + const formRef = useRef(null); + const [isVisible, setVisible] = useState(false); + const { opData, opInit, opAdd, opReplace, opReplaceItems, opRemove, opSafeKey } = useData([]); + const { columns, ...opInfo } = useColumns(awesomeColumns()); + const { rowSelection, selectedRowIDs } = useTableRowSelection(); + const { json, updateJson } = useJson(CHAT_AWESOME_JSON); + const selectedItems = rowSelection.selectedRowKeys || []; + + useEffect(() => { + if (!json || json.length <= 0) return; + opInit(json); + }, [json?.length]); + + useEffect(() => { + if (!opInfo.opType) return; + if (['edit', 'new'].includes(opInfo.opType)) { + setVisible(true); + } + if (['delete'].includes(opInfo.opType)) { + const data = opRemove(opInfo?.opRecord?.[opSafeKey]); + updateJson(data); + opInfo.resetRecord(); + } + }, [opInfo.opType, formRef]); + + const hide = () => { + setVisible(false); + opInfo.resetRecord(); + }; + + useEffect(() => { + if (opInfo.opType === 'enable') { + const data = opReplace(opInfo?.opRecord?.[opSafeKey], opInfo?.opRecord); + updateJson(data); + } + }, [opInfo.opTime]) + + const handleDelete = () => { + + }; + + const handleOk = () => { + formRef.current?.form?.validateFields() + .then(async (vals: Record) => { + if (opInfo.opType === 'new') { + const data = opAdd(vals); + await updateJson(data); + opInit(data); + message.success('Data added successfully'); + } + if (opInfo.opType === 'edit') { + const data = opReplace(opInfo?.opRecord?.[opSafeKey], vals); + await updateJson(data); + message.success('Data updated successfully'); + } + hide(); + }) + }; + + const handleEnable = (isEnable: boolean) => { + const data = opReplaceItems(selectedRowIDs, { enable: isEnable }) + updateJson(data); + }; + + const modalTitle = `${({ new: 'Create', edit: 'Edit' })[opInfo.opType]} URL`; + + return ( +
+
+ +
+ {selectedItems.length > 0 && ( + <> + + + + + + Selected {selectedItems.length} items + + )} +
+
+ +
+ + + + + ) +} \ No newline at end of file diff --git a/src/view/download/index.tsx b/src/view/download/index.tsx index f42b539..4192651 100644 --- a/src/view/download/index.tsx +++ b/src/view/download/index.tsx @@ -105,7 +105,7 @@ export default function Download() { okText="Yes" cancelText="No" > - + Selected {selectedItems.length} items diff --git a/src/view/markdown/index.scss b/src/view/markdown/index.scss index 3b61798..71f7cc5 100644 --- a/src/view/markdown/index.scss +++ b/src/view/markdown/index.scss @@ -1,6 +1,8 @@ .md-task { margin-bottom: 5px; + display: flex; + justify-content: space-between; .ant-breadcrumb-link { padding: 3px 5px; diff --git a/src/view/markdown/index.tsx b/src/view/markdown/index.tsx index ed1a165..088dd9b 100644 --- a/src/view/markdown/index.tsx +++ b/src/view/markdown/index.tsx @@ -6,12 +6,20 @@ import MarkdownEditor from '@/components/Markdown/Editor'; 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', + 1: 'md', + 2: 'doc', +} + export default function Markdown() { const [filePath, setFilePath] = useState(''); const [source, setSource] = useState(''); + const [previewMode, setPreviewMode] = useState(0); const location = useLocation(); const state = location?.state; @@ -25,6 +33,12 @@ export default function Markdown() { await fs.writeTextFile(filePath, v); }; + const handlePreview = () => { + let mode = previewMode + 1; + if (mode > 2) mode = 0; + setPreviewMode(mode); + }; + return ( <>
@@ -36,8 +50,11 @@ export default function Markdown() { {filePath} +
+ +
- + ); } \ No newline at end of file diff --git a/src/view/model/SyncCustom/Form.tsx b/src/view/model/SyncCustom/Form.tsx index adbb4c0..fbf3e3a 100644 --- a/src/view/model/SyncCustom/Form.tsx +++ b/src/view/model/SyncCustom/Form.tsx @@ -84,14 +84,14 @@ const SyncForm: ForwardRefRenderFunction = ({ record, - + { formRef.current?.form?.validateFields() .then((vals: Record) => { - let data = []; - switch (opInfo.opType) { - case 'new': data = opAdd(vals); break; - case 'edit': data = opReplace(opInfo?.opRecord?.[opSafeKey], vals); break; - default: break; + if (opInfo.opType === 'new') { + const data = opAdd(vals); + modelSet(data); + message.success('Data added successfully'); + } + if (opInfo.opType === 'edit') { + const data = opReplace(opInfo?.opRecord?.[opSafeKey], vals); + modelSet(data); + message.success('Data updated successfully'); } - modelSet(data); hide(); }) }; diff --git a/src/view/model/UserCustom/Form.tsx b/src/view/model/UserCustom/Form.tsx index 92e7a5b..c810a77 100644 --- a/src/view/model/UserCustom/Form.tsx +++ b/src/view/model/UserCustom/Form.tsx @@ -35,16 +35,16 @@ const UserCustomForm: ForwardRefRenderFunction = - + - + @@ -55,9 +55,9 @@ const UserCustomForm: ForwardRefRenderFunction = - + ) diff --git a/src/view/model/UserCustom/index.tsx b/src/view/model/UserCustom/index.tsx index 5c85e84..a98615a 100644 --- a/src/view/model/UserCustom/index.tsx +++ b/src/view/model/UserCustom/index.tsx @@ -52,14 +52,6 @@ export default function LanguageModel() { } }, [opInfo.opTime]) - - useEffect(() => { - if (opInfo.opType === 'enable') { - const data = opReplace(opInfo?.opRecord?.[opSafeKey], opInfo?.opRecord); - modelCacheSet(data); - } - }, [opInfo.opTime]); - const handleEnable = (isEnable: boolean) => { const data = opReplaceItems(selectedRowIDs, { enable: isEnable }) modelCacheSet(data); diff --git a/src/view/notes/index.tsx b/src/view/notes/index.tsx index 1ce675a..c007ea1 100644 --- a/src/view/notes/index.tsx +++ b/src/view/notes/index.tsx @@ -95,7 +95,7 @@ export default function Notes() { okText="Yes" cancelText="No" > - + Selected {selectedItems.length} items From 321007bb873a3473aa19bcf748391eeefd2e3a07 Mon Sep 17 00:00:00 2001 From: lencx Date: Sat, 21 Jan 2023 12:59:52 +0800 Subject: [PATCH 07/30] chore: add rustfmt.toml --- src-tauri/build.rs | 2 +- src-tauri/rustfmt.toml | 4 + src-tauri/src/app/cmd.rs | 537 ++++++++++++----------- src-tauri/src/app/fs_extra.rs | 139 +++--- src-tauri/src/app/menu.rs | 779 ++++++++++++++++------------------ src-tauri/src/app/setup.rs | 162 +++---- src-tauri/src/app/window.rs | 173 ++++---- src-tauri/src/conf.rs | 258 ++++++----- src-tauri/src/main.rs | 180 ++++---- src-tauri/src/utils.rs | 320 +++++++------- src/view/awesome/config.tsx | 4 +- 11 files changed, 1233 insertions(+), 1325 deletions(-) create mode 100644 src-tauri/rustfmt.toml diff --git a/src-tauri/build.rs b/src-tauri/build.rs index d860e1e..795b9b7 100644 --- a/src-tauri/build.rs +++ b/src-tauri/build.rs @@ -1,3 +1,3 @@ fn main() { - tauri_build::build() + tauri_build::build() } diff --git a/src-tauri/rustfmt.toml b/src-tauri/rustfmt.toml new file mode 100644 index 0000000..7c27671 --- /dev/null +++ b/src-tauri/rustfmt.toml @@ -0,0 +1,4 @@ +edition = "2021" +max_width = 120 +tab_spaces = 2 +newline_style = "Auto" \ No newline at end of file diff --git a/src-tauri/src/app/cmd.rs b/src-tauri/src/app/cmd.rs index b14aebd..80b0d04 100644 --- a/src-tauri/src/app/cmd.rs +++ b/src-tauri/src/app/cmd.rs @@ -1,7 +1,7 @@ use crate::{ - app::{fs_extra, window}, - conf::{ChatConfJson, GITHUB_PROMPTS_CSV_URL}, - utils::{self, chat_root, create_file}, + app::{fs_extra, window}, + conf::{ChatConfJson, GITHUB_PROMPTS_CSV_URL}, + utils::{self, chat_root, create_file}, }; use log::info; use regex::Regex; @@ -11,390 +11,383 @@ use walkdir::WalkDir; #[command] pub fn drag_window(app: AppHandle) { - app.get_window("core").unwrap().start_dragging().unwrap(); + app.get_window("core").unwrap().start_dragging().unwrap(); } #[command] pub fn dalle2_window(app: AppHandle, query: String) { - window::dalle2_window( - &app.app_handle(), - Some(query), - Some("ChatGPT & DALL·E 2".to_string()), - None, - ); + window::dalle2_window( + &app.app_handle(), + Some(query), + Some("ChatGPT & DALL·E 2".to_string()), + None, + ); } #[command] pub fn fullscreen(app: AppHandle) { - let win = app.get_window("core").unwrap(); - if win.is_fullscreen().unwrap() { - win.set_fullscreen(false).unwrap(); - } else { - win.set_fullscreen(true).unwrap(); - } + let win = app.get_window("core").unwrap(); + if win.is_fullscreen().unwrap() { + win.set_fullscreen(false).unwrap(); + } else { + win.set_fullscreen(true).unwrap(); + } } #[command] pub fn download(_app: AppHandle, name: String, blob: Vec) { - let path = chat_root().join(PathBuf::from(name)); - create_file(&path).unwrap(); - fs::write(&path, blob).unwrap(); - utils::open_file(path); + let path = chat_root().join(PathBuf::from(name)); + create_file(&path).unwrap(); + fs::write(&path, blob).unwrap(); + utils::open_file(path); } #[command] pub fn save_file(_app: AppHandle, name: String, content: String) { - let path = chat_root().join(PathBuf::from(name)); - create_file(&path).unwrap(); - fs::write(&path, content).unwrap(); - utils::open_file(path); + let path = chat_root().join(PathBuf::from(name)); + create_file(&path).unwrap(); + fs::write(&path, content).unwrap(); + utils::open_file(path); } #[command] pub fn open_link(app: AppHandle, url: String) { - api::shell::open(&app.shell_scope(), url, None).unwrap(); + api::shell::open(&app.shell_scope(), url, None).unwrap(); } #[command] pub fn get_chat_conf() -> ChatConfJson { - ChatConfJson::get_chat_conf() + ChatConfJson::get_chat_conf() } #[command] pub fn get_theme() -> String { - ChatConfJson::theme().unwrap_or(Theme::Light).to_string() + ChatConfJson::theme().unwrap_or(Theme::Light).to_string() } #[command] pub fn reset_chat_conf() -> ChatConfJson { - ChatConfJson::reset_chat_conf() + ChatConfJson::reset_chat_conf() } #[command] pub fn run_check_update(app: AppHandle, silent: bool, has_msg: Option) { - utils::run_check_update(app, silent, has_msg); + utils::run_check_update(app, silent, has_msg); } #[command] pub fn form_confirm(_app: AppHandle, data: serde_json::Value) { - ChatConfJson::amend(&serde_json::json!(data), None).unwrap(); + ChatConfJson::amend(&serde_json::json!(data), None).unwrap(); } #[command] pub fn form_cancel(app: AppHandle, label: &str, title: &str, msg: &str) { - let win = app.app_handle().get_window(label).unwrap(); - tauri::api::dialog::ask( - app.app_handle().get_window(label).as_ref(), - title, - msg, - move |is_cancel| { - if is_cancel { - win.close().unwrap(); - } - }, - ); + let win = app.app_handle().get_window(label).unwrap(); + tauri::api::dialog::ask( + app.app_handle().get_window(label).as_ref(), + title, + msg, + move |is_cancel| { + if is_cancel { + win.close().unwrap(); + } + }, + ); } #[command] pub fn form_msg(app: AppHandle, label: &str, title: &str, msg: &str) { - let win = app.app_handle().get_window(label); - tauri::api::dialog::message(win.as_ref(), title, msg); + let win = app.app_handle().get_window(label); + tauri::api::dialog::message(win.as_ref(), title, msg); } #[command] pub fn open_file(path: PathBuf) { - utils::open_file(path); + utils::open_file(path); } #[command] pub fn get_chat_model_cmd() -> serde_json::Value { - let path = utils::chat_root().join("chat.model.cmd.json"); - let content = fs::read_to_string(path).unwrap_or_else(|_| r#"{"data":[]}"#.to_string()); - serde_json::from_str(&content).unwrap() + let path = utils::chat_root().join("chat.model.cmd.json"); + let content = fs::read_to_string(path).unwrap_or_else(|_| r#"{"data":[]}"#.to_string()); + serde_json::from_str(&content).unwrap() } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct PromptRecord { - pub cmd: Option, - pub act: String, - pub prompt: String, + pub cmd: Option, + pub act: String, + pub prompt: String, } #[command] pub fn parse_prompt(data: String) -> Vec { - let mut rdr = csv::Reader::from_reader(data.as_bytes()); - let mut list = vec![]; - for result in rdr.deserialize() { - let record: PromptRecord = result.unwrap_or_else(|err| { - info!("parse_prompt_error: {}", err); - PromptRecord { - cmd: None, - act: "".to_string(), - prompt: "".to_string(), - } - }); - if !record.act.is_empty() { - list.push(record); - } + let mut rdr = csv::Reader::from_reader(data.as_bytes()); + let mut list = vec![]; + for result in rdr.deserialize() { + let record: PromptRecord = result.unwrap_or_else(|err| { + info!("parse_prompt_error: {}", err); + PromptRecord { + cmd: None, + act: "".to_string(), + prompt: "".to_string(), + } + }); + if !record.act.is_empty() { + list.push(record); } - list + } + list } #[command] pub fn window_reload(app: AppHandle, label: &str) { - app.app_handle() - .get_window(label) - .unwrap() - .eval("window.location.reload()") - .unwrap(); + app + .app_handle() + .get_window(label) + .unwrap() + .eval("window.location.reload()") + .unwrap(); } #[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] pub struct ModelRecord { - pub cmd: String, - pub act: String, - pub prompt: String, - pub tags: Vec, - pub enable: bool, + pub cmd: String, + pub act: String, + pub prompt: String, + pub tags: Vec, + pub enable: bool, } #[command] pub fn cmd_list() -> Vec { - let mut list = vec![]; - for entry in WalkDir::new(utils::chat_root().join("cache_model")) - .into_iter() - .filter_map(|e| e.ok()) - { - let file = fs::read_to_string(entry.path().display().to_string()); - if let Ok(v) = file { - let data: Vec = serde_json::from_str(&v).unwrap_or_else(|_| vec![]); - let enable_list = data.into_iter().filter(|v| v.enable); - list.extend(enable_list) - } + let mut list = vec![]; + for entry in WalkDir::new(utils::chat_root().join("cache_model")) + .into_iter() + .filter_map(|e| e.ok()) + { + let file = fs::read_to_string(entry.path().display().to_string()); + if let Ok(v) = file { + let data: Vec = serde_json::from_str(&v).unwrap_or_else(|_| vec![]); + let enable_list = data.into_iter().filter(|v| v.enable); + list.extend(enable_list) } - // dbg!(&list); - list.sort_by(|a, b| a.cmd.len().cmp(&b.cmd.len())); - list + } + // dbg!(&list); + list.sort_by(|a, b| a.cmd.len().cmp(&b.cmd.len())); + list } #[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] pub struct FileMetadata { - pub name: String, - pub ext: String, - pub created: u64, - pub id: String, + pub name: String, + pub ext: String, + pub created: u64, + pub id: String, } #[tauri::command] pub fn get_download_list(pathname: &str) -> (Vec, PathBuf) { - info!("get_download_list: {}", pathname); - let download_path = chat_root().join(PathBuf::from(pathname)); - let content = fs::read_to_string(&download_path).unwrap_or_else(|err| { - info!("download_list_error: {}", err); - fs::write(&download_path, "[]").unwrap(); - "[]".to_string() - }); - let list = serde_json::from_str::>(&content).unwrap_or_else(|err| { - info!("download_list_parse_error: {}", err); - vec![] - }); + info!("get_download_list: {}", pathname); + let download_path = chat_root().join(PathBuf::from(pathname)); + let content = fs::read_to_string(&download_path).unwrap_or_else(|err| { + info!("download_list_error: {}", err); + fs::write(&download_path, "[]").unwrap(); + "[]".to_string() + }); + let list = serde_json::from_str::>(&content).unwrap_or_else(|err| { + info!("download_list_parse_error: {}", err); + vec![] + }); - (list, download_path) + (list, download_path) } #[command] pub fn download_list(pathname: &str, dir: &str, filename: Option, id: Option) { - info!("download_list: {}", pathname); - let data = get_download_list(pathname); - let mut list = vec![]; - let mut idmap = HashMap::new(); - utils::vec_to_hashmap(data.0.into_iter(), "id", &mut idmap); + info!("download_list: {}", pathname); + let data = get_download_list(pathname); + let mut list = vec![]; + let mut idmap = HashMap::new(); + utils::vec_to_hashmap(data.0.into_iter(), "id", &mut idmap); - for entry in WalkDir::new(utils::chat_root().join(dir)) - .into_iter() - .filter_entry(|e| !utils::is_hidden(e)) - .filter_map(|e| e.ok()) - { - let metadata = entry.metadata().unwrap(); - if metadata.is_file() { - let file_path = entry.path().display().to_string(); - let re = Regex::new(r"(?P[\d\w]+).(?P\w+)$").unwrap(); - let caps = re.captures(&file_path).unwrap(); - let fid = &caps["id"]; - let fext = &caps["ext"]; + for entry in WalkDir::new(utils::chat_root().join(dir)) + .into_iter() + .filter_entry(|e| !utils::is_hidden(e)) + .filter_map(|e| e.ok()) + { + let metadata = entry.metadata().unwrap(); + if metadata.is_file() { + let file_path = entry.path().display().to_string(); + let re = Regex::new(r"(?P[\d\w]+).(?P\w+)$").unwrap(); + let caps = re.captures(&file_path).unwrap(); + let fid = &caps["id"]; + let fext = &caps["ext"]; - let mut file_data = FileMetadata { - name: fid.to_string(), - id: fid.to_string(), - ext: fext.to_string(), - created: fs_extra::system_time_to_ms(metadata.created()), - }; + let mut file_data = FileMetadata { + name: fid.to_string(), + id: fid.to_string(), + ext: fext.to_string(), + created: fs_extra::system_time_to_ms(metadata.created()), + }; - if idmap.get(fid).is_some() { - let name = idmap.get(fid).unwrap().get("name").unwrap().clone(); - match name { - serde_json::Value::String(v) => { - file_data.name = v.clone(); - v - } - _ => "".to_string(), - }; + if idmap.get(fid).is_some() { + let name = idmap.get(fid).unwrap().get("name").unwrap().clone(); + match name { + serde_json::Value::String(v) => { + file_data.name = v.clone(); + v + } + _ => "".to_string(), + }; + } + + if filename.is_some() && id.is_some() { + if let Some(ref v) = id { + if fid == v { + if let Some(ref v2) = filename { + file_data.name = v2.to_string(); } - - if filename.is_some() && id.is_some() { - if let Some(ref v) = id { - if fid == v { - if let Some(ref v2) = filename { - file_data.name = v2.to_string(); - } - } - } - } - list.push(serde_json::to_value(file_data).unwrap()); + } } + } + list.push(serde_json::to_value(file_data).unwrap()); } + } - // dbg!(&list); - list.sort_by(|a, b| { - let a1 = a.get("created").unwrap().as_u64().unwrap(); - let b1 = b.get("created").unwrap().as_u64().unwrap(); - a1.cmp(&b1).reverse() - }); + // dbg!(&list); + list.sort_by(|a, b| { + let a1 = a.get("created").unwrap().as_u64().unwrap(); + let b1 = b.get("created").unwrap().as_u64().unwrap(); + a1.cmp(&b1).reverse() + }); - fs::write(data.1, serde_json::to_string_pretty(&list).unwrap()).unwrap(); + fs::write(data.1, serde_json::to_string_pretty(&list).unwrap()).unwrap(); } #[command] pub async fn sync_prompts(app: AppHandle, time: u64) -> Option> { - let res = utils::get_data(GITHUB_PROMPTS_CSV_URL, Some(&app)) - .await - .unwrap(); + let res = utils::get_data(GITHUB_PROMPTS_CSV_URL, Some(&app)).await.unwrap(); - if let Some(v) = res { - let data = parse_prompt(v) - .iter() - .map(move |i| ModelRecord { - cmd: if i.cmd.is_some() { - i.cmd.clone().unwrap() - } else { - utils::gen_cmd(i.act.clone()) - }, - act: i.act.clone(), - prompt: i.prompt.clone(), - tags: vec!["chatgpt-prompts".to_string()], - enable: true, - }) - .collect::>(); + if let Some(v) = res { + let data = parse_prompt(v) + .iter() + .map(move |i| ModelRecord { + cmd: if i.cmd.is_some() { + i.cmd.clone().unwrap() + } else { + utils::gen_cmd(i.act.clone()) + }, + act: i.act.clone(), + prompt: i.prompt.clone(), + tags: vec!["chatgpt-prompts".to_string()], + enable: true, + }) + .collect::>(); - let data2 = data.clone(); + let data2 = data.clone(); - let model = utils::chat_root().join("chat.model.json"); - let model_cmd = utils::chat_root().join("chat.model.cmd.json"); - let chatgpt_prompts = utils::chat_root() - .join("cache_model") - .join("chatgpt_prompts.json"); + let model = utils::chat_root().join("chat.model.json"); + let model_cmd = utils::chat_root().join("chat.model.cmd.json"); + let chatgpt_prompts = utils::chat_root().join("cache_model").join("chatgpt_prompts.json"); - if !utils::exists(&model) { - fs::write( - &model, - serde_json::json!({ - "name": "ChatGPT Model", - "link": "https://github.com/lencx/ChatGPT" - }) - .to_string(), - ) - .unwrap(); - } - - // chatgpt_prompts.json - fs::write( - chatgpt_prompts, - serde_json::to_string_pretty(&data).unwrap(), - ) - .unwrap(); - let cmd_data = cmd_list(); - - // chat.model.cmd.json - fs::write( - model_cmd, - serde_json::to_string_pretty(&serde_json::json!({ - "name": "ChatGPT CMD", - "last_updated": time, - "data": cmd_data, - })) - .unwrap(), - ) - .unwrap(); - let mut kv = HashMap::new(); - kv.insert( - "sync_prompts".to_string(), - serde_json::json!({ "id": "chatgpt_prompts", "last_updated": time }), - ); - let model_data = utils::merge( - &serde_json::from_str(&fs::read_to_string(&model).unwrap()).unwrap(), - &kv, - ); - - // chat.model.json - fs::write(model, serde_json::to_string_pretty(&model_data).unwrap()).unwrap(); - - // refresh window - api::dialog::message( - app.get_window("core").as_ref(), - "Sync Prompts", - "ChatGPT Prompts data has been synchronized!", - ); - window_reload(app.clone(), "core"); - window_reload(app, "tray"); - - return Some(data2); + if !utils::exists(&model) { + fs::write( + &model, + serde_json::json!({ + "name": "ChatGPT Model", + "link": "https://github.com/lencx/ChatGPT" + }) + .to_string(), + ) + .unwrap(); } - None + // chatgpt_prompts.json + fs::write(chatgpt_prompts, serde_json::to_string_pretty(&data).unwrap()).unwrap(); + let cmd_data = cmd_list(); + + // chat.model.cmd.json + fs::write( + model_cmd, + serde_json::to_string_pretty(&serde_json::json!({ + "name": "ChatGPT CMD", + "last_updated": time, + "data": cmd_data, + })) + .unwrap(), + ) + .unwrap(); + let mut kv = HashMap::new(); + kv.insert( + "sync_prompts".to_string(), + serde_json::json!({ "id": "chatgpt_prompts", "last_updated": time }), + ); + let model_data = utils::merge( + &serde_json::from_str(&fs::read_to_string(&model).unwrap()).unwrap(), + &kv, + ); + + // chat.model.json + fs::write(model, serde_json::to_string_pretty(&model_data).unwrap()).unwrap(); + + // refresh window + api::dialog::message( + app.get_window("core").as_ref(), + "Sync Prompts", + "ChatGPT Prompts data has been synchronized!", + ); + window_reload(app.clone(), "core"); + window_reload(app, "tray"); + + return Some(data2); + } + + None } #[command] pub async fn sync_user_prompts(url: String, data_type: String) -> Option> { - let res = utils::get_data(&url, None).await.unwrap_or_else(|err| { - info!("chatgpt_http_error: {}", err); - None - }); + let res = utils::get_data(&url, None).await.unwrap_or_else(|err| { + info!("chatgpt_http_error: {}", err); + None + }); - info!("chatgpt_http_url: {}", url); + info!("chatgpt_http_url: {}", url); - if let Some(v) = res { - let data; - if data_type == "csv" { - info!("chatgpt_http_csv_parse"); - data = parse_prompt(v); - } else if data_type == "json" { - info!("chatgpt_http_json_parse"); - data = serde_json::from_str(&v).unwrap_or_else(|err| { - info!("chatgpt_http_json_parse_error: {}", err); - vec![] - }); - } else { - info!("chatgpt_http_unknown_type"); - data = vec![]; - } - - let data = data - .iter() - .map(move |i| ModelRecord { - cmd: if i.cmd.is_some() { - i.cmd.clone().unwrap() - } else { - utils::gen_cmd(i.act.clone()) - }, - act: i.act.clone(), - prompt: i.prompt.clone(), - tags: vec!["user-sync".to_string()], - enable: true, - }) - .collect::>(); - - return Some(data); + if let Some(v) = res { + let data; + if data_type == "csv" { + info!("chatgpt_http_csv_parse"); + data = parse_prompt(v); + } else if data_type == "json" { + info!("chatgpt_http_json_parse"); + data = serde_json::from_str(&v).unwrap_or_else(|err| { + info!("chatgpt_http_json_parse_error: {}", err); + vec![] + }); + } else { + info!("chatgpt_http_unknown_type"); + data = vec![]; } - None + let data = data + .iter() + .map(move |i| ModelRecord { + cmd: if i.cmd.is_some() { + i.cmd.clone().unwrap() + } else { + utils::gen_cmd(i.act.clone()) + }, + act: i.act.clone(), + prompt: i.prompt.clone(), + tags: vec!["user-sync".to_string()], + enable: true, + }) + .collect::>(); + + return Some(data); + } + + None } diff --git a/src-tauri/src/app/fs_extra.rs b/src-tauri/src/app/fs_extra.rs index 9ed9cf0..40b3c09 100644 --- a/src-tauri/src/app/fs_extra.rs +++ b/src-tauri/src/app/fs_extra.rs @@ -6,8 +6,8 @@ use serde::{ser::Serializer, Serialize}; use std::{ - path::PathBuf, - time::{SystemTime, UNIX_EPOCH}, + path::PathBuf, + time::{SystemTime, UNIX_EPOCH}, }; use tauri::command; @@ -20,101 +20,102 @@ type Result = std::result::Result; #[derive(Debug, thiserror::Error)] pub enum Error { - #[error(transparent)] - Io(#[from] std::io::Error), + #[error(transparent)] + Io(#[from] std::io::Error), } impl Serialize for Error { - fn serialize(&self, serializer: S) -> std::result::Result - where - S: Serializer, - { - serializer.serialize_str(self.to_string().as_ref()) - } + fn serialize(&self, serializer: S) -> std::result::Result + where + S: Serializer, + { + serializer.serialize_str(self.to_string().as_ref()) + } } #[derive(Serialize)] #[serde(rename_all = "camelCase")] struct Permissions { - readonly: bool, - #[cfg(unix)] - mode: u32, + readonly: bool, + #[cfg(unix)] + mode: u32, } #[cfg(unix)] #[derive(Serialize)] #[serde(rename_all = "camelCase")] struct UnixMetadata { - dev: u64, - ino: u64, - mode: u32, - nlink: u64, - uid: u32, - gid: u32, - rdev: u64, - blksize: u64, - blocks: u64, + dev: u64, + ino: u64, + mode: u32, + nlink: u64, + uid: u32, + gid: u32, + rdev: u64, + blksize: u64, + blocks: u64, } #[derive(Serialize)] #[serde(rename_all = "camelCase")] pub struct Metadata { - accessed_at_ms: u64, - pub created_at_ms: u64, - modified_at_ms: u64, - is_dir: bool, - is_file: bool, - is_symlink: bool, - size: u64, - permissions: Permissions, - #[cfg(unix)] - #[serde(flatten)] - unix: UnixMetadata, - #[cfg(windows)] - file_attributes: u32, + accessed_at_ms: u64, + pub created_at_ms: u64, + modified_at_ms: u64, + is_dir: bool, + is_file: bool, + is_symlink: bool, + size: u64, + permissions: Permissions, + #[cfg(unix)] + #[serde(flatten)] + unix: UnixMetadata, + #[cfg(windows)] + file_attributes: u32, } pub fn system_time_to_ms(time: std::io::Result) -> u64 { - time.map(|t| { - let duration_since_epoch = t.duration_since(UNIX_EPOCH).unwrap(); - duration_since_epoch.as_millis() as u64 + time + .map(|t| { + let duration_since_epoch = t.duration_since(UNIX_EPOCH).unwrap(); + duration_since_epoch.as_millis() as u64 }) .unwrap_or_default() } #[command] pub async fn metadata(path: PathBuf) -> Result { - let metadata = std::fs::metadata(path)?; - let file_type = metadata.file_type(); - let permissions = metadata.permissions(); - Ok(Metadata { - accessed_at_ms: system_time_to_ms(metadata.accessed()), - created_at_ms: system_time_to_ms(metadata.created()), - modified_at_ms: system_time_to_ms(metadata.modified()), - is_dir: file_type.is_dir(), - is_file: file_type.is_file(), - is_symlink: file_type.is_symlink(), - size: metadata.len(), - permissions: Permissions { - readonly: permissions.readonly(), - #[cfg(unix)] - mode: permissions.mode(), - }, - #[cfg(unix)] - unix: UnixMetadata { - dev: metadata.dev(), - ino: metadata.ino(), - mode: metadata.mode(), - nlink: metadata.nlink(), - uid: metadata.uid(), - gid: metadata.gid(), - rdev: metadata.rdev(), - blksize: metadata.blksize(), - blocks: metadata.blocks(), - }, - #[cfg(windows)] - file_attributes: metadata.file_attributes(), - }) + let metadata = std::fs::metadata(path)?; + let file_type = metadata.file_type(); + let permissions = metadata.permissions(); + Ok(Metadata { + accessed_at_ms: system_time_to_ms(metadata.accessed()), + created_at_ms: system_time_to_ms(metadata.created()), + modified_at_ms: system_time_to_ms(metadata.modified()), + is_dir: file_type.is_dir(), + is_file: file_type.is_file(), + is_symlink: file_type.is_symlink(), + size: metadata.len(), + permissions: Permissions { + readonly: permissions.readonly(), + #[cfg(unix)] + mode: permissions.mode(), + }, + #[cfg(unix)] + unix: UnixMetadata { + dev: metadata.dev(), + ino: metadata.ino(), + mode: metadata.mode(), + nlink: metadata.nlink(), + uid: metadata.uid(), + gid: metadata.gid(), + rdev: metadata.rdev(), + blksize: metadata.blksize(), + blocks: metadata.blocks(), + }, + #[cfg(windows)] + file_attributes: metadata.file_attributes(), + }) } // #[command] diff --git a/src-tauri/src/app/menu.rs b/src-tauri/src/app/menu.rs index 1a0faf7..25a3789 100644 --- a/src-tauri/src/app/menu.rs +++ b/src-tauri/src/app/menu.rs @@ -1,11 +1,11 @@ use crate::{ - app::{cmd, window}, - conf::{self, ChatConfJson}, - utils, + app::{cmd, window}, + conf::{self, ChatConfJson}, + utils, }; use tauri::{ - AppHandle, CustomMenuItem, Manager, Menu, MenuItem, Submenu, SystemTray, SystemTrayEvent, - SystemTrayMenu, SystemTrayMenuItem, WindowMenuEvent, + AppHandle, CustomMenuItem, Manager, Menu, MenuItem, Submenu, SystemTray, SystemTrayEvent, SystemTrayMenu, + SystemTrayMenuItem, WindowMenuEvent, }; use tauri_plugin_positioner::{on_tray_event, Position, WindowExt}; @@ -14,461 +14,404 @@ use tauri::AboutMetadata; // --- Menu pub fn init() -> Menu { - let chat_conf = ChatConfJson::get_chat_conf(); - let name = "ChatGPT"; - let app_menu = Submenu::new( - name, - Menu::with_items([ - #[cfg(target_os = "macos")] - MenuItem::About(name.into(), AboutMetadata::default()).into(), - #[cfg(not(target_os = "macos"))] - CustomMenuItem::new("about".to_string(), "About ChatGPT").into(), - CustomMenuItem::new("check_update".to_string(), "Check for Updates").into(), - MenuItem::Services.into(), - MenuItem::Hide.into(), - MenuItem::HideOthers.into(), - MenuItem::ShowAll.into(), - MenuItem::Separator.into(), - MenuItem::Quit.into(), - ]), - ); + let chat_conf = ChatConfJson::get_chat_conf(); + let name = "ChatGPT"; + let app_menu = Submenu::new( + name, + Menu::with_items([ + #[cfg(target_os = "macos")] + MenuItem::About(name.into(), AboutMetadata::default()).into(), + #[cfg(not(target_os = "macos"))] + CustomMenuItem::new("about".to_string(), "About ChatGPT").into(), + CustomMenuItem::new("check_update".to_string(), "Check for Updates").into(), + MenuItem::Services.into(), + MenuItem::Hide.into(), + MenuItem::HideOthers.into(), + MenuItem::ShowAll.into(), + MenuItem::Separator.into(), + MenuItem::Quit.into(), + ]), + ); - let stay_on_top = - CustomMenuItem::new("stay_on_top".to_string(), "Stay On Top").accelerator("CmdOrCtrl+T"); - let stay_on_top_menu = if chat_conf.stay_on_top { - stay_on_top.selected() - } else { - stay_on_top - }; + let stay_on_top = CustomMenuItem::new("stay_on_top".to_string(), "Stay On Top").accelerator("CmdOrCtrl+T"); + let stay_on_top_menu = if chat_conf.stay_on_top { + stay_on_top.selected() + } else { + stay_on_top + }; - let theme_light = CustomMenuItem::new("theme_light".to_string(), "Light"); - let theme_dark = CustomMenuItem::new("theme_dark".to_string(), "Dark"); - let theme_system = CustomMenuItem::new("theme_system".to_string(), "System"); - let is_dark = chat_conf.theme == "Dark"; - let is_system = chat_conf.theme == "System"; + let theme_light = CustomMenuItem::new("theme_light".to_string(), "Light"); + let theme_dark = CustomMenuItem::new("theme_dark".to_string(), "Dark"); + let theme_system = CustomMenuItem::new("theme_system".to_string(), "System"); + let is_dark = chat_conf.theme == "Dark"; + let is_system = chat_conf.theme == "System"; - let update_prompt = CustomMenuItem::new("update_prompt".to_string(), "Prompt"); - let update_silent = CustomMenuItem::new("update_silent".to_string(), "Silent"); - let _update_disable = CustomMenuItem::new("update_disable".to_string(), "Disable"); + let update_prompt = CustomMenuItem::new("update_prompt".to_string(), "Prompt"); + let update_silent = CustomMenuItem::new("update_silent".to_string(), "Silent"); + let _update_disable = CustomMenuItem::new("update_disable".to_string(), "Disable"); - let popup_search = CustomMenuItem::new("popup_search".to_string(), "Pop-up Search"); - let popup_search_menu = if chat_conf.popup_search { - popup_search.selected() - } else { - popup_search - }; + let popup_search = CustomMenuItem::new("popup_search".to_string(), "Pop-up Search"); + let popup_search_menu = if chat_conf.popup_search { + popup_search.selected() + } else { + popup_search + }; - #[cfg(target_os = "macos")] - let titlebar = - CustomMenuItem::new("titlebar".to_string(), "Titlebar").accelerator("CmdOrCtrl+B"); - #[cfg(target_os = "macos")] - let titlebar_menu = if chat_conf.titlebar { - titlebar.selected() - } else { - titlebar - }; + #[cfg(target_os = "macos")] + let titlebar = CustomMenuItem::new("titlebar".to_string(), "Titlebar").accelerator("CmdOrCtrl+B"); + #[cfg(target_os = "macos")] + let titlebar_menu = if chat_conf.titlebar { + titlebar.selected() + } else { + titlebar + }; - let system_tray = CustomMenuItem::new("system_tray".to_string(), "System Tray"); - let system_tray_menu = if chat_conf.tray { - system_tray.selected() - } else { - system_tray - }; + let system_tray = CustomMenuItem::new("system_tray".to_string(), "System Tray"); + let system_tray_menu = if chat_conf.tray { + system_tray.selected() + } else { + system_tray + }; - let preferences_menu = Submenu::new( - "Preferences", - Menu::with_items([ - CustomMenuItem::new("control_center".to_string(), "Control Center") - .accelerator("CmdOrCtrl+Shift+P") - .into(), - MenuItem::Separator.into(), - stay_on_top_menu.into(), - #[cfg(target_os = "macos")] - titlebar_menu.into(), - #[cfg(target_os = "macos")] - CustomMenuItem::new("hide_dock_icon".to_string(), "Hide Dock Icon").into(), - system_tray_menu.into(), - CustomMenuItem::new("inject_script".to_string(), "Inject Script") - .accelerator("CmdOrCtrl+J") - .into(), - MenuItem::Separator.into(), - Submenu::new( - "Theme", - Menu::new() - .add_item(if is_dark || is_system { - theme_light - } else { - theme_light.selected() - }) - .add_item(if is_dark { - theme_dark.selected() - } else { - theme_dark - }) - .add_item(if is_system { - theme_system.selected() - } else { - theme_system - }), - ) - .into(), - Submenu::new( - "Auto Update", - Menu::new() - .add_item(if chat_conf.auto_update == "Prompt" { - update_prompt.selected() - } else { - update_prompt - }) - .add_item(if chat_conf.auto_update == "Silent" { - update_silent.selected() - } else { - update_silent - }), // .add_item(if chat_conf.auto_update == "Disable" { - // update_disable.selected() - // } else { - // update_disable - // }) - ) - .into(), - MenuItem::Separator.into(), - popup_search_menu.into(), - CustomMenuItem::new("sync_prompts".to_string(), "Sync Prompts").into(), - MenuItem::Separator.into(), - CustomMenuItem::new("go_conf".to_string(), "Go to Config") - .accelerator("CmdOrCtrl+Shift+G") - .into(), - CustomMenuItem::new("clear_conf".to_string(), "Clear Config") - .accelerator("CmdOrCtrl+Shift+D") - .into(), - CustomMenuItem::new("restart".to_string(), "Restart ChatGPT") - .accelerator("CmdOrCtrl+Shift+R") - .into(), - MenuItem::Separator.into(), - CustomMenuItem::new("awesome".to_string(), "Awesome ChatGPT") - .accelerator("CmdOrCtrl+Shift+A") - .into(), - CustomMenuItem::new("buy_coffee".to_string(), "Buy lencx a coffee").into(), - ]), - ); - - let edit_menu = Submenu::new( - "Edit", + let preferences_menu = Submenu::new( + "Preferences", + Menu::with_items([ + CustomMenuItem::new("control_center".to_string(), "Control Center") + .accelerator("CmdOrCtrl+Shift+P") + .into(), + MenuItem::Separator.into(), + stay_on_top_menu.into(), + #[cfg(target_os = "macos")] + titlebar_menu.into(), + #[cfg(target_os = "macos")] + CustomMenuItem::new("hide_dock_icon".to_string(), "Hide Dock Icon").into(), + system_tray_menu.into(), + CustomMenuItem::new("inject_script".to_string(), "Inject Script") + .accelerator("CmdOrCtrl+J") + .into(), + MenuItem::Separator.into(), + Submenu::new( + "Theme", Menu::new() - .add_native_item(MenuItem::Undo) - .add_native_item(MenuItem::Redo) - .add_native_item(MenuItem::Separator) - .add_native_item(MenuItem::Cut) - .add_native_item(MenuItem::Copy) - .add_native_item(MenuItem::Paste) - .add_native_item(MenuItem::SelectAll), - ); - - let view_menu = Submenu::new( - "View", + .add_item(if is_dark || is_system { + theme_light + } else { + theme_light.selected() + }) + .add_item(if is_dark { theme_dark.selected() } else { theme_dark }) + .add_item(if is_system { + theme_system.selected() + } else { + theme_system + }), + ) + .into(), + Submenu::new( + "Auto Update", Menu::new() - .add_item( - CustomMenuItem::new("go_back".to_string(), "Go Back").accelerator("CmdOrCtrl+Left"), - ) - .add_item( - CustomMenuItem::new("go_forward".to_string(), "Go Forward") - .accelerator("CmdOrCtrl+Right"), - ) - .add_item( - CustomMenuItem::new("scroll_top".to_string(), "Scroll to Top of Screen") - .accelerator("CmdOrCtrl+Up"), - ) - .add_item( - CustomMenuItem::new("scroll_bottom".to_string(), "Scroll to Bottom of Screen") - .accelerator("CmdOrCtrl+Down"), - ) - .add_native_item(MenuItem::Separator) - .add_item( - CustomMenuItem::new("reload".to_string(), "Refresh the Screen") - .accelerator("CmdOrCtrl+R"), - ), - ); - - let window_menu = Submenu::new( - "Window", - Menu::new() - .add_item(CustomMenuItem::new("dalle2".to_string(), "DALL·E 2")) - .add_native_item(MenuItem::Separator) - .add_native_item(MenuItem::Minimize) - .add_native_item(MenuItem::Zoom), - ); - - let help_menu = Submenu::new( - "Help", - Menu::new() - .add_item(CustomMenuItem::new( - "chatgpt_log".to_string(), - "ChatGPT Log", - )) - .add_item(CustomMenuItem::new("update_log".to_string(), "Update Log")) - .add_item(CustomMenuItem::new("report_bug".to_string(), "Report Bug")) - .add_item( - CustomMenuItem::new("dev_tools".to_string(), "Toggle Developer Tools") - .accelerator("CmdOrCtrl+Shift+I"), - ), - ); + .add_item(if chat_conf.auto_update == "Prompt" { + update_prompt.selected() + } else { + update_prompt + }) + .add_item(if chat_conf.auto_update == "Silent" { + update_silent.selected() + } else { + update_silent + }), // .add_item(if chat_conf.auto_update == "Disable" { + // update_disable.selected() + // } else { + // update_disable + // }) + ) + .into(), + MenuItem::Separator.into(), + popup_search_menu.into(), + CustomMenuItem::new("sync_prompts".to_string(), "Sync Prompts").into(), + MenuItem::Separator.into(), + CustomMenuItem::new("go_conf".to_string(), "Go to Config") + .accelerator("CmdOrCtrl+Shift+G") + .into(), + CustomMenuItem::new("clear_conf".to_string(), "Clear Config") + .accelerator("CmdOrCtrl+Shift+D") + .into(), + CustomMenuItem::new("restart".to_string(), "Restart ChatGPT") + .accelerator("CmdOrCtrl+Shift+R") + .into(), + MenuItem::Separator.into(), + CustomMenuItem::new("awesome".to_string(), "Awesome ChatGPT") + .accelerator("CmdOrCtrl+Shift+A") + .into(), + CustomMenuItem::new("buy_coffee".to_string(), "Buy lencx a coffee").into(), + ]), + ); + let edit_menu = Submenu::new( + "Edit", Menu::new() - .add_submenu(app_menu) - .add_submenu(preferences_menu) - .add_submenu(window_menu) - .add_submenu(edit_menu) - .add_submenu(view_menu) - .add_submenu(help_menu) + .add_native_item(MenuItem::Undo) + .add_native_item(MenuItem::Redo) + .add_native_item(MenuItem::Separator) + .add_native_item(MenuItem::Cut) + .add_native_item(MenuItem::Copy) + .add_native_item(MenuItem::Paste) + .add_native_item(MenuItem::SelectAll), + ); + + let view_menu = Submenu::new( + "View", + Menu::new() + .add_item(CustomMenuItem::new("go_back".to_string(), "Go Back").accelerator("CmdOrCtrl+Left")) + .add_item(CustomMenuItem::new("go_forward".to_string(), "Go Forward").accelerator("CmdOrCtrl+Right")) + .add_item(CustomMenuItem::new("scroll_top".to_string(), "Scroll to Top of Screen").accelerator("CmdOrCtrl+Up")) + .add_item( + CustomMenuItem::new("scroll_bottom".to_string(), "Scroll to Bottom of Screen").accelerator("CmdOrCtrl+Down"), + ) + .add_native_item(MenuItem::Separator) + .add_item(CustomMenuItem::new("reload".to_string(), "Refresh the Screen").accelerator("CmdOrCtrl+R")), + ); + + let window_menu = Submenu::new( + "Window", + Menu::new() + .add_item(CustomMenuItem::new("dalle2".to_string(), "DALL·E 2")) + .add_native_item(MenuItem::Separator) + .add_native_item(MenuItem::Minimize) + .add_native_item(MenuItem::Zoom), + ); + + let help_menu = Submenu::new( + "Help", + Menu::new() + .add_item(CustomMenuItem::new("chatgpt_log".to_string(), "ChatGPT Log")) + .add_item(CustomMenuItem::new("update_log".to_string(), "Update Log")) + .add_item(CustomMenuItem::new("report_bug".to_string(), "Report Bug")) + .add_item( + CustomMenuItem::new("dev_tools".to_string(), "Toggle Developer Tools").accelerator("CmdOrCtrl+Shift+I"), + ), + ); + + Menu::new() + .add_submenu(app_menu) + .add_submenu(preferences_menu) + .add_submenu(window_menu) + .add_submenu(edit_menu) + .add_submenu(view_menu) + .add_submenu(help_menu) } // --- Menu Event pub fn menu_handler(event: WindowMenuEvent) { - let win = Some(event.window()).unwrap(); - let app = win.app_handle(); - let script_path = utils::script_path().to_string_lossy().to_string(); - let menu_id = event.menu_item_id(); - let menu_handle = win.menu_handle(); + let win = Some(event.window()).unwrap(); + let app = win.app_handle(); + let script_path = utils::script_path().to_string_lossy().to_string(); + let menu_id = event.menu_item_id(); + let menu_handle = win.menu_handle(); - match menu_id { - // App - "about" => { - let tauri_conf = utils::get_tauri_conf().unwrap(); - tauri::api::dialog::message( - app.get_window("core").as_ref(), - "ChatGPT", - format!("Version {}", tauri_conf.package.version.unwrap()), - ); + match menu_id { + // App + "about" => { + let tauri_conf = utils::get_tauri_conf().unwrap(); + tauri::api::dialog::message( + app.get_window("core").as_ref(), + "ChatGPT", + format!("Version {}", tauri_conf.package.version.unwrap()), + ); + } + "check_update" => { + utils::run_check_update(app, false, None); + } + // Preferences + "control_center" => window::control_window(&app), + "restart" => tauri::api::process::restart(&app.env()), + "inject_script" => open(&app, script_path), + "go_conf" => utils::open_file(utils::chat_root()), + "clear_conf" => utils::clear_conf(&app), + "awesome" => open(&app, conf::AWESOME_URL.to_string()), + "buy_coffee" => open(&app, conf::BUY_COFFEE.to_string()), + "popup_search" => { + let chat_conf = conf::ChatConfJson::get_chat_conf(); + let popup_search = !chat_conf.popup_search; + menu_handle.get_item(menu_id).set_selected(popup_search).unwrap(); + ChatConfJson::amend(&serde_json::json!({ "popup_search": popup_search }), None).unwrap(); + cmd::window_reload(app.clone(), "core"); + cmd::window_reload(app, "tray"); + } + "sync_prompts" => { + tauri::api::dialog::ask( + app.get_window("core").as_ref(), + "Sync Prompts", + "Data sync will enable all prompts, are you sure you want to sync?", + move |is_restart| { + if is_restart { + app + .get_window("core") + .unwrap() + .eval("window.__sync_prompts && window.__sync_prompts()") + .unwrap() + } + }, + ); + } + "hide_dock_icon" => ChatConfJson::amend(&serde_json::json!({ "hide_dock_icon": true }), Some(app)).unwrap(), + "titlebar" => { + let chat_conf = conf::ChatConfJson::get_chat_conf(); + ChatConfJson::amend(&serde_json::json!({ "titlebar": !chat_conf.titlebar }), None).unwrap(); + tauri::api::process::restart(&app.env()); + } + "system_tray" => { + let chat_conf = conf::ChatConfJson::get_chat_conf(); + ChatConfJson::amend(&serde_json::json!({ "tray": !chat_conf.tray }), None).unwrap(); + tauri::api::process::restart(&app.env()); + } + "theme_light" | "theme_dark" | "theme_system" => { + let theme = match menu_id { + "theme_dark" => "Dark", + "theme_system" => "System", + _ => "Light", + }; + ChatConfJson::amend(&serde_json::json!({ "theme": theme }), Some(app)).unwrap(); + } + "update_prompt" | "update_silent" | "update_disable" => { + // for id in ["update_prompt", "update_silent", "update_disable"] { + for id in ["update_prompt", "update_silent"] { + menu_handle.get_item(id).set_selected(false).unwrap(); + } + let auto_update = match menu_id { + "update_silent" => { + menu_handle.get_item("update_silent").set_selected(true).unwrap(); + "Silent" } - "check_update" => { - utils::run_check_update(app, false, None); + "update_disable" => { + menu_handle.get_item("update_disable").set_selected(true).unwrap(); + "Disable" } - // Preferences - "control_center" => window::control_window(&app), - "restart" => tauri::api::process::restart(&app.env()), - "inject_script" => open(&app, script_path), - "go_conf" => utils::open_file(utils::chat_root()), - "clear_conf" => utils::clear_conf(&app), - "awesome" => open(&app, conf::AWESOME_URL.to_string()), - "buy_coffee" => open(&app, conf::BUY_COFFEE.to_string()), - "popup_search" => { - let chat_conf = conf::ChatConfJson::get_chat_conf(); - let popup_search = !chat_conf.popup_search; - menu_handle - .get_item(menu_id) - .set_selected(popup_search) - .unwrap(); - ChatConfJson::amend(&serde_json::json!({ "popup_search": popup_search }), None) - .unwrap(); - cmd::window_reload(app.clone(), "core"); - cmd::window_reload(app, "tray"); + _ => { + menu_handle.get_item("update_prompt").set_selected(true).unwrap(); + "Prompt" } - "sync_prompts" => { - tauri::api::dialog::ask( - app.get_window("core").as_ref(), - "Sync Prompts", - "Data sync will enable all prompts, are you sure you want to sync?", - move |is_restart| { - if is_restart { - app.get_window("core") - .unwrap() - .eval("window.__sync_prompts && window.__sync_prompts()") - .unwrap() - } - }, - ); - } - "hide_dock_icon" => { - ChatConfJson::amend(&serde_json::json!({ "hide_dock_icon": true }), Some(app)).unwrap() - } - "titlebar" => { - let chat_conf = conf::ChatConfJson::get_chat_conf(); - ChatConfJson::amend( - &serde_json::json!({ "titlebar": !chat_conf.titlebar }), - None, - ) - .unwrap(); - tauri::api::process::restart(&app.env()); - } - "system_tray" => { - let chat_conf = conf::ChatConfJson::get_chat_conf(); - ChatConfJson::amend(&serde_json::json!({ "tray": !chat_conf.tray }), None).unwrap(); - tauri::api::process::restart(&app.env()); - } - "theme_light" | "theme_dark" | "theme_system" => { - let theme = match menu_id { - "theme_dark" => "Dark", - "theme_system" => "System", - _ => "Light", - }; - ChatConfJson::amend(&serde_json::json!({ "theme": theme }), Some(app)).unwrap(); - } - "update_prompt" | "update_silent" | "update_disable" => { - // for id in ["update_prompt", "update_silent", "update_disable"] { - for id in ["update_prompt", "update_silent"] { - menu_handle.get_item(id).set_selected(false).unwrap(); - } - let auto_update = match menu_id { - "update_silent" => { - menu_handle - .get_item("update_silent") - .set_selected(true) - .unwrap(); - "Silent" - } - "update_disable" => { - menu_handle - .get_item("update_disable") - .set_selected(true) - .unwrap(); - "Disable" - } - _ => { - menu_handle - .get_item("update_prompt") - .set_selected(true) - .unwrap(); - "Prompt" - } - }; - ChatConfJson::amend(&serde_json::json!({ "auto_update": auto_update }), None).unwrap(); - } - "stay_on_top" => { - let chat_conf = conf::ChatConfJson::get_chat_conf(); - let stay_on_top = !chat_conf.stay_on_top; - menu_handle - .get_item(menu_id) - .set_selected(stay_on_top) - .unwrap(); - win.set_always_on_top(stay_on_top).unwrap(); - ChatConfJson::amend(&serde_json::json!({ "stay_on_top": stay_on_top }), None).unwrap(); - } - // Window - "dalle2" => window::dalle2_window(&app, None, None, Some(false)), - // View - "reload" => win.eval("window.location.reload()").unwrap(), - "go_back" => win.eval("window.history.go(-1)").unwrap(), - "go_forward" => win.eval("window.history.go(1)").unwrap(), - // core: document.querySelector('main .overflow-y-auto') - "scroll_top" => win - .eval( - r#"window.scroll({ + }; + ChatConfJson::amend(&serde_json::json!({ "auto_update": auto_update }), None).unwrap(); + } + "stay_on_top" => { + let chat_conf = conf::ChatConfJson::get_chat_conf(); + let stay_on_top = !chat_conf.stay_on_top; + menu_handle.get_item(menu_id).set_selected(stay_on_top).unwrap(); + win.set_always_on_top(stay_on_top).unwrap(); + ChatConfJson::amend(&serde_json::json!({ "stay_on_top": stay_on_top }), None).unwrap(); + } + // Window + "dalle2" => window::dalle2_window(&app, None, None, Some(false)), + // View + "reload" => win.eval("window.location.reload()").unwrap(), + "go_back" => win.eval("window.history.go(-1)").unwrap(), + "go_forward" => win.eval("window.history.go(1)").unwrap(), + // core: document.querySelector('main .overflow-y-auto') + "scroll_top" => win + .eval( + r#"window.scroll({ top: 0, left: 0, behavior: "smooth" })"#, - ) - .unwrap(), - "scroll_bottom" => win - .eval( - r#"window.scroll({ + ) + .unwrap(), + "scroll_bottom" => win + .eval( + r#"window.scroll({ top: document.body.scrollHeight, left: 0, behavior: "smooth"})"#, - ) - .unwrap(), - // Help - "chatgpt_log" => utils::open_file(utils::chat_root().join("chatgpt.log")), - "update_log" => open(&app, conf::UPDATE_LOG_URL.to_string()), - "report_bug" => open(&app, conf::ISSUES_URL.to_string()), - "dev_tools" => { - win.open_devtools(); - win.close_devtools(); - } - _ => (), + ) + .unwrap(), + // Help + "chatgpt_log" => utils::open_file(utils::chat_root().join("chatgpt.log")), + "update_log" => open(&app, conf::UPDATE_LOG_URL.to_string()), + "report_bug" => open(&app, conf::ISSUES_URL.to_string()), + "dev_tools" => { + win.open_devtools(); + win.close_devtools(); } + _ => (), + } } // --- SystemTray Menu pub fn tray_menu() -> SystemTray { - if cfg!(target_os = "macos") { - SystemTray::new().with_menu( - SystemTrayMenu::new() - .add_item(CustomMenuItem::new( - "control_center".to_string(), - "Control Center", - )) - .add_native_item(SystemTrayMenuItem::Separator) - .add_item(CustomMenuItem::new( - "show_dock_icon".to_string(), - "Show Dock Icon", - )) - .add_item(CustomMenuItem::new( - "hide_dock_icon".to_string(), - "Hide Dock Icon", - )) - .add_item(CustomMenuItem::new("show_core".to_string(), "Show ChatGPT")) - .add_native_item(SystemTrayMenuItem::Separator) - .add_item(CustomMenuItem::new("quit".to_string(), "Quit ChatGPT")), - ) - } else { - SystemTray::new().with_menu( - SystemTrayMenu::new() - .add_item(CustomMenuItem::new( - "control_center".to_string(), - "Control Center", - )) - .add_item(CustomMenuItem::new("show_core".to_string(), "Show ChatGPT")) - .add_native_item(SystemTrayMenuItem::Separator) - .add_item(CustomMenuItem::new("quit".to_string(), "Quit ChatGPT")), - ) - } + if cfg!(target_os = "macos") { + SystemTray::new().with_menu( + SystemTrayMenu::new() + .add_item(CustomMenuItem::new("control_center".to_string(), "Control Center")) + .add_native_item(SystemTrayMenuItem::Separator) + .add_item(CustomMenuItem::new("show_dock_icon".to_string(), "Show Dock Icon")) + .add_item(CustomMenuItem::new("hide_dock_icon".to_string(), "Hide Dock Icon")) + .add_item(CustomMenuItem::new("show_core".to_string(), "Show ChatGPT")) + .add_native_item(SystemTrayMenuItem::Separator) + .add_item(CustomMenuItem::new("quit".to_string(), "Quit ChatGPT")), + ) + } else { + SystemTray::new().with_menu( + SystemTrayMenu::new() + .add_item(CustomMenuItem::new("control_center".to_string(), "Control Center")) + .add_item(CustomMenuItem::new("show_core".to_string(), "Show ChatGPT")) + .add_native_item(SystemTrayMenuItem::Separator) + .add_item(CustomMenuItem::new("quit".to_string(), "Quit ChatGPT")), + ) + } } // --- SystemTray Event pub fn tray_handler(handle: &AppHandle, event: SystemTrayEvent) { - on_tray_event(handle, &event); + on_tray_event(handle, &event); - let app = handle.clone(); + let app = handle.clone(); - match event { - SystemTrayEvent::LeftClick { .. } => { - let chat_conf = conf::ChatConfJson::get_chat_conf(); + match event { + SystemTrayEvent::LeftClick { .. } => { + let chat_conf = conf::ChatConfJson::get_chat_conf(); - if !chat_conf.hide_dock_icon { - let core_win = handle.get_window("core").unwrap(); - core_win.minimize().unwrap(); - } + if !chat_conf.hide_dock_icon { + let core_win = handle.get_window("core").unwrap(); + core_win.minimize().unwrap(); + } - let tray_win = handle.get_window("tray").unwrap(); - tray_win.move_window(Position::TrayCenter).unwrap(); + let tray_win = handle.get_window("tray").unwrap(); + tray_win.move_window(Position::TrayCenter).unwrap(); - if tray_win.is_visible().unwrap() { - tray_win.hide().unwrap(); - } else { - tray_win.show().unwrap(); - } - } - SystemTrayEvent::MenuItemClick { id, .. } => match id.as_str() { - "control_center" => window::control_window(&app), - "restart" => tauri::api::process::restart(&handle.env()), - "show_dock_icon" => { - ChatConfJson::amend(&serde_json::json!({ "hide_dock_icon": false }), Some(app)) - .unwrap(); - } - "hide_dock_icon" => { - let chat_conf = conf::ChatConfJson::get_chat_conf(); - if !chat_conf.hide_dock_icon { - ChatConfJson::amend(&serde_json::json!({ "hide_dock_icon": true }), Some(app)) - .unwrap(); - } - } - "show_core" => { - let core_win = app.get_window("core").unwrap(); - let tray_win = app.get_window("tray").unwrap(); - if !core_win.is_visible().unwrap() { - core_win.show().unwrap(); - core_win.set_focus().unwrap(); - tray_win.hide().unwrap(); - } - } - "quit" => std::process::exit(0), - _ => (), - }, - _ => (), + if tray_win.is_visible().unwrap() { + tray_win.hide().unwrap(); + } else { + tray_win.show().unwrap(); + } } + SystemTrayEvent::MenuItemClick { id, .. } => match id.as_str() { + "control_center" => window::control_window(&app), + "restart" => tauri::api::process::restart(&handle.env()), + "show_dock_icon" => { + ChatConfJson::amend(&serde_json::json!({ "hide_dock_icon": false }), Some(app)).unwrap(); + } + "hide_dock_icon" => { + let chat_conf = conf::ChatConfJson::get_chat_conf(); + if !chat_conf.hide_dock_icon { + ChatConfJson::amend(&serde_json::json!({ "hide_dock_icon": true }), Some(app)).unwrap(); + } + } + "show_core" => { + let core_win = app.get_window("core").unwrap(); + let tray_win = app.get_window("tray").unwrap(); + if !core_win.is_visible().unwrap() { + core_win.show().unwrap(); + core_win.set_focus().unwrap(); + tray_win.hide().unwrap(); + } + } + "quit" => std::process::exit(0), + _ => (), + }, + _ => (), + } } pub fn open(app: &AppHandle, path: String) { - tauri::api::shell::open(&app.shell_scope(), path, None).unwrap(); + tauri::api::shell::open(&app.shell_scope(), path, None).unwrap(); } diff --git a/src-tauri/src/app/setup.rs b/src-tauri/src/app/setup.rs index 9c2d5cd..ab2c133 100644 --- a/src-tauri/src/app/setup.rs +++ b/src-tauri/src/app/setup.rs @@ -4,90 +4,90 @@ use tauri::{utils::config::WindowUrl, window::WindowBuilder, App, GlobalShortcut use wry::application::accelerator::Accelerator; pub fn init(app: &mut App) -> std::result::Result<(), Box> { - info!("stepup"); - let chat_conf = ChatConfJson::get_chat_conf(); - let url = chat_conf.origin.to_string(); - let theme = ChatConfJson::theme(); - let handle = app.app_handle(); + info!("stepup"); + let chat_conf = ChatConfJson::get_chat_conf(); + let url = chat_conf.origin.to_string(); + let theme = ChatConfJson::theme(); + let handle = app.app_handle(); + tauri::async_runtime::spawn(async move { + window::tray_window(&handle); + }); + + if let Some(v) = chat_conf.global_shortcut { + info!("global_shortcut: `{}`", v); + match v.parse::() { + Ok(_) => { + info!("global_shortcut_register"); + let handle = app.app_handle(); + let mut shortcut = app.global_shortcut_manager(); + shortcut + .register(&v, move || { + if let Some(w) = handle.get_window("core") { + if w.is_visible().unwrap() { + w.hide().unwrap(); + } else { + w.show().unwrap(); + w.set_focus().unwrap(); + } + } + }) + .unwrap_or_else(|err| { + info!("global_shortcut_register_error: {}", err); + }); + } + Err(err) => { + info!("global_shortcut_parse_error: {}", err); + } + } + } else { + info!("global_shortcut_unregister"); + }; + + if chat_conf.hide_dock_icon { + #[cfg(target_os = "macos")] + app.set_activation_policy(tauri::ActivationPolicy::Accessory); + } else { + let app = app.handle(); tauri::async_runtime::spawn(async move { - window::tray_window(&handle); + let mut main_win = WindowBuilder::new(&app, "core", WindowUrl::App(url.into())) + .title("ChatGPT") + .resizable(true) + .fullscreen(false) + .inner_size(800.0, 600.0); + + if cfg!(target_os = "macos") { + main_win = main_win.hidden_title(true); + } + + main_win + .theme(theme) + .always_on_top(chat_conf.stay_on_top) + .title_bar_style(ChatConfJson::titlebar()) + .initialization_script(&utils::user_script()) + .initialization_script(include_str!("../vendors/floating-ui-core.js")) + .initialization_script(include_str!("../vendors/floating-ui-dom.js")) + .initialization_script(include_str!("../vendors/html2canvas.js")) + .initialization_script(include_str!("../vendors/jspdf.js")) + .initialization_script(include_str!("../vendors/turndown.js")) + .initialization_script(include_str!("../vendors/turndown-plugin-gfm.js")) + .initialization_script(include_str!("../scripts/core.js")) + .initialization_script(include_str!("../scripts/popup.core.js")) + .initialization_script(include_str!("../scripts/export.js")) + .initialization_script(include_str!("../scripts/markdown.export.js")) + .initialization_script(include_str!("../scripts/cmd.js")) + .user_agent(&chat_conf.ua_window) + .build() + .unwrap(); }); + } - if let Some(v) = chat_conf.global_shortcut { - info!("global_shortcut: `{}`", v); - match v.parse::() { - Ok(_) => { - info!("global_shortcut_register"); - let handle = app.app_handle(); - let mut shortcut = app.global_shortcut_manager(); - shortcut - .register(&v, move || { - if let Some(w) = handle.get_window("core") { - if w.is_visible().unwrap() { - w.hide().unwrap(); - } else { - w.show().unwrap(); - w.set_focus().unwrap(); - } - } - }) - .unwrap_or_else(|err| { - info!("global_shortcut_register_error: {}", err); - }); - } - Err(err) => { - info!("global_shortcut_parse_error: {}", err); - } - } - } else { - info!("global_shortcut_unregister"); - }; + // auto_update + if chat_conf.auto_update != "Disable" { + info!("stepup::run_check_update"); + let app = app.handle(); + utils::run_check_update(app, chat_conf.auto_update == "Silent", None); + } - if chat_conf.hide_dock_icon { - #[cfg(target_os = "macos")] - app.set_activation_policy(tauri::ActivationPolicy::Accessory); - } else { - let app = app.handle(); - tauri::async_runtime::spawn(async move { - let mut main_win = WindowBuilder::new(&app, "core", WindowUrl::App(url.into())) - .title("ChatGPT") - .resizable(true) - .fullscreen(false) - .inner_size(800.0, 600.0); - - if cfg!(target_os = "macos") { - main_win = main_win.hidden_title(true); - } - - main_win - .theme(theme) - .always_on_top(chat_conf.stay_on_top) - .title_bar_style(ChatConfJson::titlebar()) - .initialization_script(&utils::user_script()) - .initialization_script(include_str!("../vendors/floating-ui-core.js")) - .initialization_script(include_str!("../vendors/floating-ui-dom.js")) - .initialization_script(include_str!("../vendors/html2canvas.js")) - .initialization_script(include_str!("../vendors/jspdf.js")) - .initialization_script(include_str!("../vendors/turndown.js")) - .initialization_script(include_str!("../vendors/turndown-plugin-gfm.js")) - .initialization_script(include_str!("../scripts/core.js")) - .initialization_script(include_str!("../scripts/popup.core.js")) - .initialization_script(include_str!("../scripts/export.js")) - .initialization_script(include_str!("../scripts/markdown.export.js")) - .initialization_script(include_str!("../scripts/cmd.js")) - .user_agent(&chat_conf.ua_window) - .build() - .unwrap(); - }); - } - - // auto_update - if chat_conf.auto_update != "Disable" { - info!("stepup::run_check_update"); - let app = app.handle(); - utils::run_check_update(app, chat_conf.auto_update == "Silent", None); - } - - Ok(()) + Ok(()) } diff --git a/src-tauri/src/app/window.rs b/src-tauri/src/app/window.rs index d614214..507e633 100644 --- a/src-tauri/src/app/window.rs +++ b/src-tauri/src/app/window.rs @@ -4,104 +4,95 @@ use std::time::SystemTime; use tauri::{utils::config::WindowUrl, window::WindowBuilder, Manager}; pub fn tray_window(handle: &tauri::AppHandle) { - let chat_conf = conf::ChatConfJson::get_chat_conf(); - let theme = conf::ChatConfJson::theme(); - let app = handle.clone(); + let chat_conf = conf::ChatConfJson::get_chat_conf(); + let theme = conf::ChatConfJson::theme(); + let app = handle.clone(); - tauri::async_runtime::spawn(async move { - WindowBuilder::new(&app, "tray", WindowUrl::App(chat_conf.origin.into())) - .title("ChatGPT") - .resizable(false) - .fullscreen(false) - .inner_size(360.0, 540.0) - .decorations(false) - .always_on_top(true) - .theme(theme) - .initialization_script(&utils::user_script()) - .initialization_script(include_str!("../vendors/floating-ui-core.js")) - .initialization_script(include_str!("../vendors/floating-ui-dom.js")) - .initialization_script(include_str!("../scripts/core.js")) - .initialization_script(include_str!("../scripts/cmd.js")) - .initialization_script(include_str!("../scripts/popup.core.js")) - .user_agent(&chat_conf.ua_tray) - .build() - .unwrap() - .hide() - .unwrap(); - }); + tauri::async_runtime::spawn(async move { + WindowBuilder::new(&app, "tray", WindowUrl::App(chat_conf.origin.into())) + .title("ChatGPT") + .resizable(false) + .fullscreen(false) + .inner_size(360.0, 540.0) + .decorations(false) + .always_on_top(true) + .theme(theme) + .initialization_script(&utils::user_script()) + .initialization_script(include_str!("../vendors/floating-ui-core.js")) + .initialization_script(include_str!("../vendors/floating-ui-dom.js")) + .initialization_script(include_str!("../scripts/core.js")) + .initialization_script(include_str!("../scripts/cmd.js")) + .initialization_script(include_str!("../scripts/popup.core.js")) + .user_agent(&chat_conf.ua_tray) + .build() + .unwrap() + .hide() + .unwrap(); + }); } -pub fn dalle2_window( - handle: &tauri::AppHandle, - query: Option, - title: Option, - is_new: Option, -) { - info!("dalle2_query: {:?}", query); - let theme = conf::ChatConfJson::theme(); - let app = handle.clone(); +pub fn dalle2_window(handle: &tauri::AppHandle, query: Option, title: Option, is_new: Option) { + info!("dalle2_query: {:?}", query); + let theme = conf::ChatConfJson::theme(); + let app = handle.clone(); - let query = if query.is_some() { - format!( - "window.addEventListener('DOMContentLoaded', function() {{\nwindow.__CHATGPT_QUERY__='{}';\n}})", - query.unwrap() - ) - } else { - "".to_string() - }; + let query = if query.is_some() { + format!( + "window.addEventListener('DOMContentLoaded', function() {{\nwindow.__CHATGPT_QUERY__='{}';\n}})", + query.unwrap() + ) + } else { + "".to_string() + }; - let label = if is_new.unwrap_or(true) { - let timestamp = SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .unwrap() - .as_secs(); - format!("dalle2_{}", timestamp) - } else { - "dalle2".to_string() - }; + let label = if is_new.unwrap_or(true) { + let timestamp = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + .as_secs(); + format!("dalle2_{}", timestamp) + } else { + "dalle2".to_string() + }; - if app.get_window("dalle2").is_none() { - tauri::async_runtime::spawn(async move { - WindowBuilder::new( - &app, - label, - WindowUrl::App("https://labs.openai.com".into()), - ) - .title(title.unwrap_or_else(|| "DALL·E 2".to_string())) - .resizable(true) - .fullscreen(false) - .inner_size(800.0, 600.0) - .always_on_top(false) - .theme(theme) - .initialization_script(include_str!("../scripts/core.js")) - .initialization_script(&query) - .initialization_script(include_str!("../scripts/dalle2.js")) - .build() - .unwrap(); - }); - } else { - let dalle2_win = app.get_window("dalle2").unwrap(); - dalle2_win.show().unwrap(); - dalle2_win.set_focus().unwrap(); - } + if app.get_window("dalle2").is_none() { + tauri::async_runtime::spawn(async move { + WindowBuilder::new(&app, label, WindowUrl::App("https://labs.openai.com".into())) + .title(title.unwrap_or_else(|| "DALL·E 2".to_string())) + .resizable(true) + .fullscreen(false) + .inner_size(800.0, 600.0) + .always_on_top(false) + .theme(theme) + .initialization_script(include_str!("../scripts/core.js")) + .initialization_script(&query) + .initialization_script(include_str!("../scripts/dalle2.js")) + .build() + .unwrap(); + }); + } else { + let dalle2_win = app.get_window("dalle2").unwrap(); + dalle2_win.show().unwrap(); + dalle2_win.set_focus().unwrap(); + } } pub fn control_window(handle: &tauri::AppHandle) { - let app = handle.clone(); - tauri::async_runtime::spawn(async move { - if app.app_handle().get_window("main").is_none() { - WindowBuilder::new(&app, "main", WindowUrl::App("index.html".into())) - .title("Control Center") - .resizable(true) - .fullscreen(false) - .inner_size(1000.0, 700.0) - .min_inner_size(800.0, 600.0) - .build() - .unwrap(); - } else { - let main_win = app.app_handle().get_window("main").unwrap(); - main_win.show().unwrap(); - main_win.set_focus().unwrap(); - } - }); + let app = handle.clone(); + tauri::async_runtime::spawn(async move { + if app.app_handle().get_window("main").is_none() { + WindowBuilder::new(&app, "main", WindowUrl::App("index.html".into())) + .title("Control Center") + .resizable(true) + .fullscreen(false) + .inner_size(1000.0, 700.0) + .min_inner_size(800.0, 600.0) + .build() + .unwrap(); + } else { + let main_win = app.app_handle().get_window("main").unwrap(); + main_win.show().unwrap(); + main_win.set_focus().unwrap(); + } + }); } diff --git a/src-tauri/src/conf.rs b/src-tauri/src/conf.rs index 46d5307..557ca6d 100644 --- a/src-tauri/src/conf.rs +++ b/src-tauri/src/conf.rs @@ -15,8 +15,7 @@ pub const ISSUES_URL: &str = "https://github.com/lencx/ChatGPT/issues"; pub const UPDATE_LOG_URL: &str = "https://github.com/lencx/ChatGPT/blob/main/UPDATE_LOG.md"; pub const AWESOME_URL: &str = "https://github.com/lencx/ChatGPT/blob/main/AWESOME.md"; pub const BUY_COFFEE: &str = "https://www.buymeacoffee.com/lencx"; -pub const GITHUB_PROMPTS_CSV_URL: &str = - "https://raw.githubusercontent.com/f/awesome-chatgpt-prompts/main/prompts.csv"; +pub const GITHUB_PROMPTS_CSV_URL: &str = "https://raw.githubusercontent.com/f/awesome-chatgpt-prompts/main/prompts.csv"; pub const DEFAULT_CHAT_CONF: &str = r#"{ "stay_on_top": false, "auto_update": "Prompt", @@ -48,153 +47,150 @@ pub const DEFAULT_CHAT_CONF_MAC: &str = r#"{ #[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] pub struct ChatConfJson { - // support macOS only - pub titlebar: bool, - pub hide_dock_icon: bool, + // support macOS only + pub titlebar: bool, + pub hide_dock_icon: bool, - // macOS and Windows, Light/Dark/System - pub theme: String, - // auto update policy, Prompt/Silent/Disable - pub auto_update: String, - pub tray: bool, - pub popup_search: bool, - pub stay_on_top: bool, - pub default_origin: String, - pub origin: String, - pub ua_window: String, - pub ua_tray: String, - pub global_shortcut: Option, + // macOS and Windows, Light/Dark/System + pub theme: String, + // auto update policy, Prompt/Silent/Disable + pub auto_update: String, + pub tray: bool, + pub popup_search: bool, + pub stay_on_top: bool, + pub default_origin: String, + pub origin: String, + pub ua_window: String, + pub ua_tray: String, + pub global_shortcut: Option, } impl ChatConfJson { - /// init chat.conf.json - /// path: ~/.chatgpt/chat.conf.json - pub fn init() -> PathBuf { - info!("chat_conf_init"); - let conf_file = ChatConfJson::conf_path(); - let content = if cfg!(target_os = "macos") { - DEFAULT_CHAT_CONF_MAC - } else { - DEFAULT_CHAT_CONF - }; + /// init chat.conf.json + /// path: ~/.chatgpt/chat.conf.json + pub fn init() -> PathBuf { + info!("chat_conf_init"); + let conf_file = ChatConfJson::conf_path(); + let content = if cfg!(target_os = "macos") { + DEFAULT_CHAT_CONF_MAC + } else { + DEFAULT_CHAT_CONF + }; - if !exists(&conf_file) { - create_file(&conf_file).unwrap(); - fs::write(&conf_file, content).unwrap(); - return conf_file; + if !exists(&conf_file) { + create_file(&conf_file).unwrap(); + fs::write(&conf_file, content).unwrap(); + return conf_file; + } + + let conf_file = ChatConfJson::conf_path(); + let file_content = fs::read_to_string(&conf_file).unwrap(); + match serde_json::from_str(&file_content) { + Ok(v) => v, + Err(err) => { + if err.to_string() == "invalid type: map, expected unit at line 1 column 0" { + return conf_file; } + fs::write(&conf_file, content).unwrap(); + } + }; - let conf_file = ChatConfJson::conf_path(); - let file_content = fs::read_to_string(&conf_file).unwrap(); - match serde_json::from_str(&file_content) { - Ok(v) => v, - Err(err) => { - if err.to_string() == "invalid type: map, expected unit at line 1 column 0" { - return conf_file; - } - fs::write(&conf_file, content).unwrap(); - } - }; + conf_file + } - conf_file - } + pub fn conf_path() -> PathBuf { + chat_root().join("chat.conf.json") + } - pub fn conf_path() -> PathBuf { - chat_root().join("chat.conf.json") - } + pub fn get_chat_conf() -> Self { + let conf_file = ChatConfJson::conf_path(); + let file_content = fs::read_to_string(&conf_file).unwrap(); + let content = if cfg!(target_os = "macos") { + DEFAULT_CHAT_CONF_MAC + } else { + DEFAULT_CHAT_CONF + }; - pub fn get_chat_conf() -> Self { - let conf_file = ChatConfJson::conf_path(); - let file_content = fs::read_to_string(&conf_file).unwrap(); - let content = if cfg!(target_os = "macos") { - DEFAULT_CHAT_CONF_MAC - } else { - DEFAULT_CHAT_CONF - }; - - match serde_json::from_value(match serde_json::from_str(&file_content) { - Ok(v) => v, - Err(_) => { - fs::write(&conf_file, content).unwrap(); - serde_json::from_str(content).unwrap() - } - }) { - Ok(v) => v, - Err(_) => { - fs::write(&conf_file, content).unwrap(); - serde_json::from_value(serde_json::from_str(content).unwrap()).unwrap() - } - } - } - - pub fn reset_chat_conf() -> Self { - let conf_file = ChatConfJson::conf_path(); - let content = if cfg!(target_os = "macos") { - DEFAULT_CHAT_CONF_MAC - } else { - DEFAULT_CHAT_CONF - }; + match serde_json::from_value(match serde_json::from_str(&file_content) { + Ok(v) => v, + Err(_) => { fs::write(&conf_file, content).unwrap(); serde_json::from_str(content).unwrap() + } + }) { + Ok(v) => v, + Err(_) => { + fs::write(&conf_file, content).unwrap(); + serde_json::from_value(serde_json::from_str(content).unwrap()).unwrap() + } + } + } + + pub fn reset_chat_conf() -> Self { + let conf_file = ChatConfJson::conf_path(); + let content = if cfg!(target_os = "macos") { + DEFAULT_CHAT_CONF_MAC + } else { + DEFAULT_CHAT_CONF + }; + fs::write(&conf_file, content).unwrap(); + serde_json::from_str(content).unwrap() + } + + // https://users.rust-lang.org/t/updating-object-fields-given-dynamic-json/39049/3 + pub fn amend(new_rules: &Value, app: Option) -> Result<()> { + let config = ChatConfJson::get_chat_conf(); + let config: Value = serde_json::to_value(&config)?; + let mut config: BTreeMap = serde_json::from_value(config)?; + let new_rules: BTreeMap = serde_json::from_value(new_rules.clone())?; + + for (k, v) in new_rules { + config.insert(k, v); } - // https://users.rust-lang.org/t/updating-object-fields-given-dynamic-json/39049/3 - pub fn amend(new_rules: &Value, app: Option) -> Result<()> { - let config = ChatConfJson::get_chat_conf(); - let config: Value = serde_json::to_value(&config)?; - let mut config: BTreeMap = serde_json::from_value(config)?; - let new_rules: BTreeMap = serde_json::from_value(new_rules.clone())?; + fs::write(ChatConfJson::conf_path(), serde_json::to_string_pretty(&config)?)?; - for (k, v) in new_rules { - config.insert(k, v); - } - - fs::write( - ChatConfJson::conf_path(), - serde_json::to_string_pretty(&config)?, - )?; - - if let Some(handle) = app { - tauri::api::process::restart(&handle.env()); - // tauri::api::dialog::ask( - // handle.get_window("core").as_ref(), - // "ChatGPT Restart", - // "Whether to restart immediately?", - // move |is_restart| { - // if is_restart { - // } - // }, - // ); - } - - Ok(()) + if let Some(handle) = app { + tauri::api::process::restart(&handle.env()); + // tauri::api::dialog::ask( + // handle.get_window("core").as_ref(), + // "ChatGPT Restart", + // "Whether to restart immediately?", + // move |is_restart| { + // if is_restart { + // } + // }, + // ); } - pub fn theme() -> Option { - let conf = ChatConfJson::get_chat_conf(); - let theme = match conf.theme.as_str() { - "System" => match dark_light::detect() { - // Dark mode - dark_light::Mode::Dark => Theme::Dark, - // Light mode - dark_light::Mode::Light => Theme::Light, - // Unspecified - dark_light::Mode::Default => Theme::Light, - }, - "Dark" => Theme::Dark, - _ => Theme::Light, - }; + Ok(()) + } - Some(theme) - } + pub fn theme() -> Option { + let conf = ChatConfJson::get_chat_conf(); + let theme = match conf.theme.as_str() { + "System" => match dark_light::detect() { + // Dark mode + dark_light::Mode::Dark => Theme::Dark, + // Light mode + dark_light::Mode::Light => Theme::Light, + // Unspecified + dark_light::Mode::Default => Theme::Light, + }, + "Dark" => Theme::Dark, + _ => Theme::Light, + }; - #[cfg(target_os = "macos")] - pub fn titlebar() -> TitleBarStyle { - let conf = ChatConfJson::get_chat_conf(); - if conf.titlebar { - TitleBarStyle::Transparent - } else { - TitleBarStyle::Overlay - } + Some(theme) + } + + #[cfg(target_os = "macos")] + pub fn titlebar() -> TitleBarStyle { + let conf = ChatConfJson::get_chat_conf(); + if conf.titlebar { + TitleBarStyle::Transparent + } else { + TitleBarStyle::Overlay } + } } diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index bed964b..40b9072 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -1,7 +1,4 @@ -#![cfg_attr( - all(not(debug_assertions), target_os = "windows"), - windows_subsystem = "windows" -)] +#![cfg_attr(all(not(debug_assertions), target_os = "windows"), windows_subsystem = "windows")] mod app; mod conf; @@ -12,104 +9,101 @@ use conf::ChatConfJson; use tauri::api::path; use tauri_plugin_autostart::MacosLauncher; use tauri_plugin_log::{ - fern::colors::{Color, ColoredLevelConfig}, - LogTarget, + fern::colors::{Color, ColoredLevelConfig}, + LogTarget, }; #[tokio::main] async fn main() { - ChatConfJson::init(); - // If the file does not exist, creating the file will block menu synchronization - utils::create_chatgpt_prompts(); - let context = tauri::generate_context!(); - let colors = ColoredLevelConfig { - error: Color::Red, - warn: Color::Yellow, - debug: Color::Blue, - info: Color::BrightGreen, - trace: Color::Cyan, - }; + ChatConfJson::init(); + // If the file does not exist, creating the file will block menu synchronization + utils::create_chatgpt_prompts(); + let context = tauri::generate_context!(); + let colors = ColoredLevelConfig { + error: Color::Red, + warn: Color::Yellow, + debug: Color::Blue, + info: Color::BrightGreen, + trace: Color::Cyan, + }; - cmd::download_list("chat.download.json", "download", None, None); - cmd::download_list("chat.notes.json", "notes", None, None); + cmd::download_list("chat.download.json", "download", None, None); + cmd::download_list("chat.notes.json", "notes", None, None); - let chat_conf = ChatConfJson::get_chat_conf(); + let chat_conf = ChatConfJson::get_chat_conf(); - let mut builder = tauri::Builder::default() - // https://github.com/tauri-apps/tauri/pull/2736 - .plugin( - tauri_plugin_log::Builder::default() - .targets([ - // LogTarget::LogDir, - // LOG PATH: ~/.chatgpt/ChatGPT.log - LogTarget::Folder(path::home_dir().unwrap().join(".chatgpt")), - LogTarget::Stdout, - LogTarget::Webview, - ]) - .level(log::LevelFilter::Debug) - .with_colors(colors) - .build(), - ) - .plugin(tauri_plugin_positioner::init()) - .plugin(tauri_plugin_autostart::init( - MacosLauncher::LaunchAgent, - None, - )) - .invoke_handler(tauri::generate_handler![ - cmd::drag_window, - cmd::fullscreen, - cmd::download, - cmd::save_file, - cmd::open_link, - cmd::get_chat_conf, - cmd::get_theme, - cmd::reset_chat_conf, - cmd::run_check_update, - cmd::form_cancel, - cmd::form_confirm, - cmd::form_msg, - cmd::open_file, - cmd::get_chat_model_cmd, - cmd::parse_prompt, - cmd::sync_prompts, - cmd::sync_user_prompts, - cmd::window_reload, - cmd::dalle2_window, - cmd::cmd_list, - cmd::download_list, - cmd::get_download_list, - fs_extra::metadata, + let mut builder = tauri::Builder::default() + // https://github.com/tauri-apps/tauri/pull/2736 + .plugin( + tauri_plugin_log::Builder::default() + .targets([ + // LogTarget::LogDir, + // LOG PATH: ~/.chatgpt/ChatGPT.log + LogTarget::Folder(path::home_dir().unwrap().join(".chatgpt")), + LogTarget::Stdout, + LogTarget::Webview, ]) - .setup(setup::init) - .menu(menu::init()); + .level(log::LevelFilter::Debug) + .with_colors(colors) + .build(), + ) + .plugin(tauri_plugin_positioner::init()) + .plugin(tauri_plugin_autostart::init(MacosLauncher::LaunchAgent, None)) + .invoke_handler(tauri::generate_handler![ + cmd::drag_window, + cmd::fullscreen, + cmd::download, + cmd::save_file, + cmd::open_link, + cmd::get_chat_conf, + cmd::get_theme, + cmd::reset_chat_conf, + cmd::run_check_update, + cmd::form_cancel, + cmd::form_confirm, + cmd::form_msg, + cmd::open_file, + cmd::get_chat_model_cmd, + cmd::parse_prompt, + cmd::sync_prompts, + cmd::sync_user_prompts, + cmd::window_reload, + cmd::dalle2_window, + cmd::cmd_list, + cmd::download_list, + cmd::get_download_list, + fs_extra::metadata, + ]) + .setup(setup::init) + .menu(menu::init()); - if chat_conf.tray { - builder = builder.system_tray(menu::tray_menu()); - } + if chat_conf.tray { + builder = builder.system_tray(menu::tray_menu()); + } - builder - .on_menu_event(menu::menu_handler) - .on_system_tray_event(menu::tray_handler) - .on_window_event(|event| { - // https://github.com/tauri-apps/tauri/discussions/2684 - if let tauri::WindowEvent::CloseRequested { api, .. } = event.event() { - let win = event.window(); - if win.label() == "core" { - // TODO: https://github.com/tauri-apps/tauri/issues/3084 - // event.window().hide().unwrap(); - // https://github.com/tauri-apps/tao/pull/517 - #[cfg(target_os = "macos")] - event.window().minimize().unwrap(); + builder + .on_menu_event(menu::menu_handler) + .on_system_tray_event(menu::tray_handler) + .on_window_event(|event| { + // https://github.com/tauri-apps/tauri/discussions/2684 + if let tauri::WindowEvent::CloseRequested { api, .. } = event.event() { + let win = event.window(); + if win.label() == "core" { + // TODO: https://github.com/tauri-apps/tauri/issues/3084 + // event.window().hide().unwrap(); + // https://github.com/tauri-apps/tao/pull/517 + #[cfg(target_os = "macos")] + event.window().minimize().unwrap(); - // fix: https://github.com/lencx/ChatGPT/issues/93 - #[cfg(not(target_os = "macos"))] - event.window().hide().unwrap(); - } else { - win.close().unwrap(); - } - api.prevent_close(); - } - }) - .run(context) - .expect("error while running ChatGPT application"); + // fix: https://github.com/lencx/ChatGPT/issues/93 + #[cfg(not(target_os = "macos"))] + event.window().hide().unwrap(); + } else { + win.close().unwrap(); + } + api.prevent_close(); + } + }) + .run(context) + .expect("error while running ChatGPT application"); } diff --git a/src-tauri/src/utils.rs b/src-tauri/src/utils.rs index a5625ec..9261e56 100644 --- a/src-tauri/src/utils.rs +++ b/src-tauri/src/utils.rs @@ -3,215 +3,180 @@ use log::info; use regex::Regex; use serde_json::Value; use std::{ - collections::HashMap, - fs::{self, File}, - path::{Path, PathBuf}, - process::Command, + collections::HashMap, + fs::{self, File}, + path::{Path, PathBuf}, + process::Command, }; use tauri::updater::UpdateResponse; use tauri::{utils::config::Config, AppHandle, Manager, Wry}; pub fn chat_root() -> PathBuf { - tauri::api::path::home_dir().unwrap().join(".chatgpt") + tauri::api::path::home_dir().unwrap().join(".chatgpt") } pub fn get_tauri_conf() -> Option { - let config_file = include_str!("../tauri.conf.json"); - let config: Config = - serde_json::from_str(config_file).expect("failed to parse tauri.conf.json"); - Some(config) + let config_file = include_str!("../tauri.conf.json"); + let config: Config = serde_json::from_str(config_file).expect("failed to parse tauri.conf.json"); + Some(config) } pub fn exists(path: &Path) -> bool { - Path::new(path).exists() + Path::new(path).exists() } pub fn create_file(path: &Path) -> Result { - if let Some(p) = path.parent() { - fs::create_dir_all(p)? - } - File::create(path).map_err(Into::into) + if let Some(p) = path.parent() { + fs::create_dir_all(p)? + } + File::create(path).map_err(Into::into) } pub fn create_chatgpt_prompts() { - let sync_file = chat_root().join("cache_model").join("chatgpt_prompts.json"); - if !exists(&sync_file) { - create_file(&sync_file).unwrap(); - fs::write(&sync_file, "[]").unwrap(); - } + let sync_file = chat_root().join("cache_model").join("chatgpt_prompts.json"); + if !exists(&sync_file) { + create_file(&sync_file).unwrap(); + fs::write(&sync_file, "[]").unwrap(); + } } pub fn script_path() -> PathBuf { - let script_file = chat_root().join("main.js"); - if !exists(&script_file) { - create_file(&script_file).unwrap(); - fs::write(&script_file, format!("// *** ChatGPT User Script ***\n// @github: https://github.com/lencx/ChatGPT \n// @path: {}\n\nconsole.log('🤩 Hello ChatGPT!!!');", &script_file.to_string_lossy())).unwrap(); - } + let script_file = chat_root().join("main.js"); + if !exists(&script_file) { + create_file(&script_file).unwrap(); + fs::write(&script_file, format!("// *** ChatGPT User Script ***\n// @github: https://github.com/lencx/ChatGPT \n// @path: {}\n\nconsole.log('🤩 Hello ChatGPT!!!');", &script_file.to_string_lossy())).unwrap(); + } - script_file + script_file } pub fn user_script() -> String { - let user_script_content = fs::read_to_string(script_path()).unwrap_or_else(|_| "".to_string()); - format!( - "window.addEventListener('DOMContentLoaded', function() {{\n{}\n}})", - user_script_content - ) + let user_script_content = fs::read_to_string(script_path()).unwrap_or_else(|_| "".to_string()); + format!( + "window.addEventListener('DOMContentLoaded', function() {{\n{}\n}})", + user_script_content + ) } pub fn open_file(path: PathBuf) { - info!("open_file: {}", path.to_string_lossy()); - #[cfg(target_os = "macos")] - Command::new("open").arg("-R").arg(path).spawn().unwrap(); + info!("open_file: {}", path.to_string_lossy()); + #[cfg(target_os = "macos")] + Command::new("open").arg("-R").arg(path).spawn().unwrap(); - #[cfg(target_os = "windows")] - Command::new("explorer") - .arg("/select,") - .arg(path) - .spawn() - .unwrap(); + #[cfg(target_os = "windows")] + Command::new("explorer").arg("/select,").arg(path).spawn().unwrap(); - // https://askubuntu.com/a/31071 - #[cfg(target_os = "linux")] - Command::new("xdg-open").arg(path).spawn().unwrap(); + // https://askubuntu.com/a/31071 + #[cfg(target_os = "linux")] + Command::new("xdg-open").arg(path).spawn().unwrap(); } pub fn clear_conf(app: &tauri::AppHandle) { - let root = chat_root(); - let app2 = app.clone(); - let msg = format!("Path: {}\nAre you sure to clear all ChatGPT configurations? Please backup in advance if necessary!", root.to_string_lossy()); - tauri::api::dialog::ask( - app.get_window("core").as_ref(), - "Clear Config", - msg, - move |is_ok| { - if is_ok { - fs::remove_dir_all(root).unwrap(); - tauri::api::process::restart(&app2.env()); - } - }, - ); + let root = chat_root(); + let app2 = app.clone(); + let msg = format!( + "Path: {}\nAre you sure to clear all ChatGPT configurations? Please backup in advance if necessary!", + root.to_string_lossy() + ); + tauri::api::dialog::ask(app.get_window("core").as_ref(), "Clear Config", msg, move |is_ok| { + if is_ok { + fs::remove_dir_all(root).unwrap(); + tauri::api::process::restart(&app2.env()); + } + }); } pub fn merge(v: &Value, fields: &HashMap) -> Value { - match v { - Value::Object(m) => { - let mut m = m.clone(); - for (k, v) in fields { - m.insert(k.clone(), v.clone()); - } - Value::Object(m) - } - v => v.clone(), + match v { + Value::Object(m) => { + let mut m = m.clone(); + for (k, v) in fields { + m.insert(k.clone(), v.clone()); + } + Value::Object(m) } + v => v.clone(), + } } pub fn gen_cmd(name: String) -> String { - let re = Regex::new(r"[^a-zA-Z0-9]").unwrap(); - re.replace_all(&name, "_").to_lowercase() + let re = Regex::new(r"[^a-zA-Z0-9]").unwrap(); + re.replace_all(&name, "_").to_lowercase() } -pub async fn get_data( - url: &str, - app: Option<&tauri::AppHandle>, -) -> Result, reqwest::Error> { - let res = reqwest::get(url).await?; - let is_ok = res.status() == 200; - let body = res.text().await?; +pub async fn get_data(url: &str, app: Option<&tauri::AppHandle>) -> Result, reqwest::Error> { + let res = reqwest::get(url).await?; + let is_ok = res.status() == 200; + let body = res.text().await?; - if is_ok { - Ok(Some(body)) - } else { - info!("chatgpt_http_error: {}", body); - if let Some(v) = app { - tauri::api::dialog::message(v.get_window("core").as_ref(), "ChatGPT HTTP", body); - } - Ok(None) + if is_ok { + Ok(Some(body)) + } else { + info!("chatgpt_http_error: {}", body); + if let Some(v) = app { + tauri::api::dialog::message(v.get_window("core").as_ref(), "ChatGPT HTTP", body); } + Ok(None) + } } pub fn run_check_update(app: AppHandle, silent: bool, has_msg: Option) { - info!("run_check_update: silent={} has_msg={:?}", silent, has_msg); - tauri::async_runtime::spawn(async move { - let result = app.updater().check().await; - let update_resp = result.unwrap(); - if update_resp.is_update_available() { - if silent { - tauri::async_runtime::spawn(async move { - silent_install(app, update_resp).await.unwrap(); - }); - } else { - tauri::async_runtime::spawn(async move { - prompt_for_install(app, update_resp).await.unwrap(); - }); - } - } else if let Some(v) = has_msg { - if v { - tauri::api::dialog::message( - app.app_handle().get_window("core").as_ref(), - "ChatGPT", - "Your ChatGPT is up to date", - ); - } - } - }); + info!("run_check_update: silent={} has_msg={:?}", silent, has_msg); + tauri::async_runtime::spawn(async move { + let result = app.updater().check().await; + let update_resp = result.unwrap(); + if update_resp.is_update_available() { + if silent { + tauri::async_runtime::spawn(async move { + silent_install(app, update_resp).await.unwrap(); + }); + } else { + tauri::async_runtime::spawn(async move { + prompt_for_install(app, update_resp).await.unwrap(); + }); + } + } else if let Some(v) = has_msg { + if v { + tauri::api::dialog::message( + app.app_handle().get_window("core").as_ref(), + "ChatGPT", + "Your ChatGPT is up to date", + ); + } + } + }); } // Copy private api in tauri/updater/mod.rs. TODO: refactor to public api // Prompt a dialog asking if the user want to install the new version // Maybe we should add an option to customize it in future versions. pub async fn prompt_for_install(app: AppHandle, update: UpdateResponse) -> Result<()> { - info!("prompt_for_install"); - let windows = app.windows(); - let parent_window = windows.values().next(); - let package_info = app.package_info().clone(); + info!("prompt_for_install"); + let windows = app.windows(); + let parent_window = windows.values().next(); + let package_info = app.package_info().clone(); - let body = update.body().unwrap(); - // todo(lemarier): We should review this and make sure we have - // something more conventional. - let should_install = tauri::api::dialog::blocking::ask( - parent_window, - format!(r#"A new version of {} is available! "#, package_info.name), - format!( - r#"{} {} is now available -- you have {}. + let body = update.body().unwrap(); + // todo(lemarier): We should review this and make sure we have + // something more conventional. + let should_install = tauri::api::dialog::blocking::ask( + parent_window, + format!(r#"A new version of {} is available! "#, package_info.name), + format!( + r#"{} {} is now available -- you have {}. Would you like to install it now? Release Notes: {}"#, - package_info.name, - update.latest_version(), - package_info.version, - body - ), - ); - - if should_install { - // Launch updater download process - // macOS we display the `Ready to restart dialog` asking to restart - // Windows is closing the current App and launch the downloaded MSI when ready (the process stop here) - // Linux we replace the AppImage by launching a new install, it start a new AppImage instance, so we're closing the previous. (the process stop here) - update.download_and_install().await?; - - // Ask user if we need to restart the application - let should_exit = tauri::api::dialog::blocking::ask( - parent_window, - "Ready to Restart", - "The installation was successful, do you want to restart the application now?", - ); - if should_exit { - app.restart(); - } - } - - Ok(()) -} - -pub async fn silent_install(app: AppHandle, update: UpdateResponse) -> Result<()> { - info!("silent_install"); - let windows = app.windows(); - let parent_window = windows.values().next(); + package_info.name, + update.latest_version(), + package_info.version, + body + ), + ); + if should_install { // Launch updater download process // macOS we display the `Ready to restart dialog` asking to restart // Windows is closing the current App and launch the downloaded MSI when ready (the process stop here) @@ -220,33 +185,54 @@ pub async fn silent_install(app: AppHandle, update: UpdateResponse) -> // Ask user if we need to restart the application let should_exit = tauri::api::dialog::blocking::ask( - parent_window, - "Ready to Restart", - "The silent installation was successful, do you want to restart the application now?", + parent_window, + "Ready to Restart", + "The installation was successful, do you want to restart the application now?", ); if should_exit { - app.restart(); + app.restart(); } + } - Ok(()) + Ok(()) +} + +pub async fn silent_install(app: AppHandle, update: UpdateResponse) -> Result<()> { + info!("silent_install"); + let windows = app.windows(); + let parent_window = windows.values().next(); + + // Launch updater download process + // macOS we display the `Ready to restart dialog` asking to restart + // Windows is closing the current App and launch the downloaded MSI when ready (the process stop here) + // Linux we replace the AppImage by launching a new install, it start a new AppImage instance, so we're closing the previous. (the process stop here) + update.download_and_install().await?; + + // Ask user if we need to restart the application + let should_exit = tauri::api::dialog::blocking::ask( + parent_window, + "Ready to Restart", + "The silent installation was successful, do you want to restart the application now?", + ); + if should_exit { + app.restart(); + } + + Ok(()) } pub fn is_hidden(entry: &walkdir::DirEntry) -> bool { - entry - .file_name() - .to_str() - .map(|s| s.starts_with('.')) - .unwrap_or(false) + entry.file_name().to_str().map(|s| s.starts_with('.')).unwrap_or(false) } pub fn vec_to_hashmap( - vec: impl Iterator, - key: &str, - map: &mut HashMap, + vec: impl Iterator, + key: &str, + map: &mut HashMap, ) { - for v in vec { - if let Some(kval) = v.get(key).and_then(serde_json::Value::as_str) { - map.insert(kval.to_string(), v); - } + for v in vec { + if let Some(kval) = v.get(key).and_then(serde_json::Value::as_str) { + map.insert(kval.to_string(), v); } + } } diff --git a/src/view/awesome/config.tsx b/src/view/awesome/config.tsx index 208c752..4e27247 100644 --- a/src/view/awesome/config.tsx +++ b/src/view/awesome/config.tsx @@ -12,7 +12,7 @@ export const awesomeColumns = () => [ title: 'URL', dataIndex: 'url', key: 'url', - width: 120, + width: 200, }, // { // title: 'Icon', @@ -33,7 +33,7 @@ export const awesomeColumns = () => [ title: 'Category', dataIndex: 'category', key: 'category', - width: 200, + width: 120, render: (v: string) => {v} }, { From 5b6a69444ea268ee00c704ae5b34ba6162f77305 Mon Sep 17 00:00:00 2001 From: lencx Date: Sat, 21 Jan 2023 13:25:20 +0800 Subject: [PATCH 08/30] chore: fmt --- src-tauri/rustfmt.toml | 16 +++++-- src-tauri/src/app/cmd.rs | 14 ++++-- src-tauri/src/app/menu.rs | 92 ++++++++++++++++++++++++++++--------- src-tauri/src/app/window.rs | 35 ++++++++------ src-tauri/src/conf.rs | 8 +++- src-tauri/src/main.rs | 10 +++- src-tauri/src/utils.rs | 34 ++++++++++---- 7 files changed, 156 insertions(+), 53 deletions(-) diff --git a/src-tauri/rustfmt.toml b/src-tauri/rustfmt.toml index 7c27671..37a4436 100644 --- a/src-tauri/rustfmt.toml +++ b/src-tauri/rustfmt.toml @@ -1,4 +1,14 @@ -edition = "2021" -max_width = 120 +max_width = 100 +hard_tabs = false tab_spaces = 2 -newline_style = "Auto" \ No newline at end of file +newline_style = "Auto" +use_small_heuristics = "Default" +reorder_imports = true +reorder_modules = true +remove_nested_parens = true +edition = "2021" +merge_derives = true +use_try_shorthand = false +use_field_init_shorthand = false +force_explicit_abi = true +# imports_granularity = "Crate" \ No newline at end of file diff --git a/src-tauri/src/app/cmd.rs b/src-tauri/src/app/cmd.rs index 80b0d04..4b87e4f 100644 --- a/src-tauri/src/app/cmd.rs +++ b/src-tauri/src/app/cmd.rs @@ -267,7 +267,9 @@ pub fn download_list(pathname: &str, dir: &str, filename: Option, id: Op #[command] pub async fn sync_prompts(app: AppHandle, time: u64) -> Option> { - let res = utils::get_data(GITHUB_PROMPTS_CSV_URL, Some(&app)).await.unwrap(); + let res = utils::get_data(GITHUB_PROMPTS_CSV_URL, Some(&app)) + .await + .unwrap(); if let Some(v) = res { let data = parse_prompt(v) @@ -289,7 +291,9 @@ pub async fn sync_prompts(app: AppHandle, time: u64) -> Option> let model = utils::chat_root().join("chat.model.json"); let model_cmd = utils::chat_root().join("chat.model.cmd.json"); - let chatgpt_prompts = utils::chat_root().join("cache_model").join("chatgpt_prompts.json"); + let chatgpt_prompts = utils::chat_root() + .join("cache_model") + .join("chatgpt_prompts.json"); if !utils::exists(&model) { fs::write( @@ -304,7 +308,11 @@ pub async fn sync_prompts(app: AppHandle, time: u64) -> Option> } // chatgpt_prompts.json - fs::write(chatgpt_prompts, serde_json::to_string_pretty(&data).unwrap()).unwrap(); + fs::write( + chatgpt_prompts, + serde_json::to_string_pretty(&data).unwrap(), + ) + .unwrap(); let cmd_data = cmd_list(); // chat.model.cmd.json diff --git a/src-tauri/src/app/menu.rs b/src-tauri/src/app/menu.rs index 25a3789..9d7d440 100644 --- a/src-tauri/src/app/menu.rs +++ b/src-tauri/src/app/menu.rs @@ -4,8 +4,8 @@ use crate::{ utils, }; use tauri::{ - AppHandle, CustomMenuItem, Manager, Menu, MenuItem, Submenu, SystemTray, SystemTrayEvent, SystemTrayMenu, - SystemTrayMenuItem, WindowMenuEvent, + AppHandle, CustomMenuItem, Manager, Menu, MenuItem, Submenu, SystemTray, SystemTrayEvent, + SystemTrayMenu, SystemTrayMenuItem, WindowMenuEvent, }; use tauri_plugin_positioner::{on_tray_event, Position, WindowExt}; @@ -33,7 +33,8 @@ pub fn init() -> Menu { ]), ); - let stay_on_top = CustomMenuItem::new("stay_on_top".to_string(), "Stay On Top").accelerator("CmdOrCtrl+T"); + let stay_on_top = + CustomMenuItem::new("stay_on_top".to_string(), "Stay On Top").accelerator("CmdOrCtrl+T"); let stay_on_top_menu = if chat_conf.stay_on_top { stay_on_top.selected() } else { @@ -98,7 +99,11 @@ pub fn init() -> Menu { } else { theme_light.selected() }) - .add_item(if is_dark { theme_dark.selected() } else { theme_dark }) + .add_item(if is_dark { + theme_dark.selected() + } else { + theme_dark + }) .add_item(if is_system { theme_system.selected() } else { @@ -162,13 +167,21 @@ pub fn init() -> Menu { "View", Menu::new() .add_item(CustomMenuItem::new("go_back".to_string(), "Go Back").accelerator("CmdOrCtrl+Left")) - .add_item(CustomMenuItem::new("go_forward".to_string(), "Go Forward").accelerator("CmdOrCtrl+Right")) - .add_item(CustomMenuItem::new("scroll_top".to_string(), "Scroll to Top of Screen").accelerator("CmdOrCtrl+Up")) .add_item( - CustomMenuItem::new("scroll_bottom".to_string(), "Scroll to Bottom of Screen").accelerator("CmdOrCtrl+Down"), + CustomMenuItem::new("go_forward".to_string(), "Go Forward").accelerator("CmdOrCtrl+Right"), + ) + .add_item( + CustomMenuItem::new("scroll_top".to_string(), "Scroll to Top of Screen") + .accelerator("CmdOrCtrl+Up"), + ) + .add_item( + CustomMenuItem::new("scroll_bottom".to_string(), "Scroll to Bottom of Screen") + .accelerator("CmdOrCtrl+Down"), ) .add_native_item(MenuItem::Separator) - .add_item(CustomMenuItem::new("reload".to_string(), "Refresh the Screen").accelerator("CmdOrCtrl+R")), + .add_item( + CustomMenuItem::new("reload".to_string(), "Refresh the Screen").accelerator("CmdOrCtrl+R"), + ), ); let window_menu = Submenu::new( @@ -183,11 +196,15 @@ pub fn init() -> Menu { let help_menu = Submenu::new( "Help", Menu::new() - .add_item(CustomMenuItem::new("chatgpt_log".to_string(), "ChatGPT Log")) + .add_item(CustomMenuItem::new( + "chatgpt_log".to_string(), + "ChatGPT Log", + )) .add_item(CustomMenuItem::new("update_log".to_string(), "Update Log")) .add_item(CustomMenuItem::new("report_bug".to_string(), "Report Bug")) .add_item( - CustomMenuItem::new("dev_tools".to_string(), "Toggle Developer Tools").accelerator("CmdOrCtrl+Shift+I"), + CustomMenuItem::new("dev_tools".to_string(), "Toggle Developer Tools") + .accelerator("CmdOrCtrl+Shift+I"), ), ); @@ -232,7 +249,10 @@ pub fn menu_handler(event: WindowMenuEvent) { "popup_search" => { let chat_conf = conf::ChatConfJson::get_chat_conf(); let popup_search = !chat_conf.popup_search; - menu_handle.get_item(menu_id).set_selected(popup_search).unwrap(); + menu_handle + .get_item(menu_id) + .set_selected(popup_search) + .unwrap(); ChatConfJson::amend(&serde_json::json!({ "popup_search": popup_search }), None).unwrap(); cmd::window_reload(app.clone(), "core"); cmd::window_reload(app, "tray"); @@ -253,10 +273,16 @@ pub fn menu_handler(event: WindowMenuEvent) { }, ); } - "hide_dock_icon" => ChatConfJson::amend(&serde_json::json!({ "hide_dock_icon": true }), Some(app)).unwrap(), + "hide_dock_icon" => { + ChatConfJson::amend(&serde_json::json!({ "hide_dock_icon": true }), Some(app)).unwrap() + } "titlebar" => { let chat_conf = conf::ChatConfJson::get_chat_conf(); - ChatConfJson::amend(&serde_json::json!({ "titlebar": !chat_conf.titlebar }), None).unwrap(); + ChatConfJson::amend( + &serde_json::json!({ "titlebar": !chat_conf.titlebar }), + None, + ) + .unwrap(); tauri::api::process::restart(&app.env()); } "system_tray" => { @@ -279,15 +305,24 @@ pub fn menu_handler(event: WindowMenuEvent) { } let auto_update = match menu_id { "update_silent" => { - menu_handle.get_item("update_silent").set_selected(true).unwrap(); + menu_handle + .get_item("update_silent") + .set_selected(true) + .unwrap(); "Silent" } "update_disable" => { - menu_handle.get_item("update_disable").set_selected(true).unwrap(); + menu_handle + .get_item("update_disable") + .set_selected(true) + .unwrap(); "Disable" } _ => { - menu_handle.get_item("update_prompt").set_selected(true).unwrap(); + menu_handle + .get_item("update_prompt") + .set_selected(true) + .unwrap(); "Prompt" } }; @@ -296,7 +331,10 @@ pub fn menu_handler(event: WindowMenuEvent) { "stay_on_top" => { let chat_conf = conf::ChatConfJson::get_chat_conf(); let stay_on_top = !chat_conf.stay_on_top; - menu_handle.get_item(menu_id).set_selected(stay_on_top).unwrap(); + menu_handle + .get_item(menu_id) + .set_selected(stay_on_top) + .unwrap(); win.set_always_on_top(stay_on_top).unwrap(); ChatConfJson::amend(&serde_json::json!({ "stay_on_top": stay_on_top }), None).unwrap(); } @@ -341,10 +379,19 @@ pub fn tray_menu() -> SystemTray { if cfg!(target_os = "macos") { SystemTray::new().with_menu( SystemTrayMenu::new() - .add_item(CustomMenuItem::new("control_center".to_string(), "Control Center")) + .add_item(CustomMenuItem::new( + "control_center".to_string(), + "Control Center", + )) .add_native_item(SystemTrayMenuItem::Separator) - .add_item(CustomMenuItem::new("show_dock_icon".to_string(), "Show Dock Icon")) - .add_item(CustomMenuItem::new("hide_dock_icon".to_string(), "Hide Dock Icon")) + .add_item(CustomMenuItem::new( + "show_dock_icon".to_string(), + "Show Dock Icon", + )) + .add_item(CustomMenuItem::new( + "hide_dock_icon".to_string(), + "Hide Dock Icon", + )) .add_item(CustomMenuItem::new("show_core".to_string(), "Show ChatGPT")) .add_native_item(SystemTrayMenuItem::Separator) .add_item(CustomMenuItem::new("quit".to_string(), "Quit ChatGPT")), @@ -352,7 +399,10 @@ pub fn tray_menu() -> SystemTray { } else { SystemTray::new().with_menu( SystemTrayMenu::new() - .add_item(CustomMenuItem::new("control_center".to_string(), "Control Center")) + .add_item(CustomMenuItem::new( + "control_center".to_string(), + "Control Center", + )) .add_item(CustomMenuItem::new("show_core".to_string(), "Show ChatGPT")) .add_native_item(SystemTrayMenuItem::Separator) .add_item(CustomMenuItem::new("quit".to_string(), "Quit ChatGPT")), diff --git a/src-tauri/src/app/window.rs b/src-tauri/src/app/window.rs index 507e633..1b34874 100644 --- a/src-tauri/src/app/window.rs +++ b/src-tauri/src/app/window.rs @@ -31,7 +31,12 @@ pub fn tray_window(handle: &tauri::AppHandle) { }); } -pub fn dalle2_window(handle: &tauri::AppHandle, query: Option, title: Option, is_new: Option) { +pub fn dalle2_window( + handle: &tauri::AppHandle, + query: Option, + title: Option, + is_new: Option, +) { info!("dalle2_query: {:?}", query); let theme = conf::ChatConfJson::theme(); let app = handle.clone(); @@ -57,18 +62,22 @@ pub fn dalle2_window(handle: &tauri::AppHandle, query: Option, title: Op if app.get_window("dalle2").is_none() { tauri::async_runtime::spawn(async move { - WindowBuilder::new(&app, label, WindowUrl::App("https://labs.openai.com".into())) - .title(title.unwrap_or_else(|| "DALL·E 2".to_string())) - .resizable(true) - .fullscreen(false) - .inner_size(800.0, 600.0) - .always_on_top(false) - .theme(theme) - .initialization_script(include_str!("../scripts/core.js")) - .initialization_script(&query) - .initialization_script(include_str!("../scripts/dalle2.js")) - .build() - .unwrap(); + WindowBuilder::new( + &app, + label, + WindowUrl::App("https://labs.openai.com".into()), + ) + .title(title.unwrap_or_else(|| "DALL·E 2".to_string())) + .resizable(true) + .fullscreen(false) + .inner_size(800.0, 600.0) + .always_on_top(false) + .theme(theme) + .initialization_script(include_str!("../scripts/core.js")) + .initialization_script(&query) + .initialization_script(include_str!("../scripts/dalle2.js")) + .build() + .unwrap(); }); } else { let dalle2_win = app.get_window("dalle2").unwrap(); diff --git a/src-tauri/src/conf.rs b/src-tauri/src/conf.rs index 557ca6d..b70d68c 100644 --- a/src-tauri/src/conf.rs +++ b/src-tauri/src/conf.rs @@ -15,7 +15,8 @@ pub const ISSUES_URL: &str = "https://github.com/lencx/ChatGPT/issues"; pub const UPDATE_LOG_URL: &str = "https://github.com/lencx/ChatGPT/blob/main/UPDATE_LOG.md"; pub const AWESOME_URL: &str = "https://github.com/lencx/ChatGPT/blob/main/AWESOME.md"; pub const BUY_COFFEE: &str = "https://www.buymeacoffee.com/lencx"; -pub const GITHUB_PROMPTS_CSV_URL: &str = "https://raw.githubusercontent.com/f/awesome-chatgpt-prompts/main/prompts.csv"; +pub const GITHUB_PROMPTS_CSV_URL: &str = + "https://raw.githubusercontent.com/f/awesome-chatgpt-prompts/main/prompts.csv"; pub const DEFAULT_CHAT_CONF: &str = r#"{ "stay_on_top": false, "auto_update": "Prompt", @@ -148,7 +149,10 @@ impl ChatConfJson { config.insert(k, v); } - fs::write(ChatConfJson::conf_path(), serde_json::to_string_pretty(&config)?)?; + fs::write( + ChatConfJson::conf_path(), + serde_json::to_string_pretty(&config)?, + )?; if let Some(handle) = app { tauri::api::process::restart(&handle.env()); diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 40b9072..0468ac8 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -1,4 +1,7 @@ -#![cfg_attr(all(not(debug_assertions), target_os = "windows"), windows_subsystem = "windows")] +#![cfg_attr( + all(not(debug_assertions), target_os = "windows"), + windows_subsystem = "windows" +)] mod app; mod conf; @@ -48,7 +51,10 @@ async fn main() { .build(), ) .plugin(tauri_plugin_positioner::init()) - .plugin(tauri_plugin_autostart::init(MacosLauncher::LaunchAgent, None)) + .plugin(tauri_plugin_autostart::init( + MacosLauncher::LaunchAgent, + None, + )) .invoke_handler(tauri::generate_handler![ cmd::drag_window, cmd::fullscreen, diff --git a/src-tauri/src/utils.rs b/src-tauri/src/utils.rs index 9261e56..63cd7dd 100644 --- a/src-tauri/src/utils.rs +++ b/src-tauri/src/utils.rs @@ -64,7 +64,11 @@ pub fn open_file(path: PathBuf) { Command::new("open").arg("-R").arg(path).spawn().unwrap(); #[cfg(target_os = "windows")] - Command::new("explorer").arg("/select,").arg(path).spawn().unwrap(); + Command::new("explorer") + .arg("/select,") + .arg(path) + .spawn() + .unwrap(); // https://askubuntu.com/a/31071 #[cfg(target_os = "linux")] @@ -78,12 +82,17 @@ pub fn clear_conf(app: &tauri::AppHandle) { "Path: {}\nAre you sure to clear all ChatGPT configurations? Please backup in advance if necessary!", root.to_string_lossy() ); - tauri::api::dialog::ask(app.get_window("core").as_ref(), "Clear Config", msg, move |is_ok| { - if is_ok { - fs::remove_dir_all(root).unwrap(); - tauri::api::process::restart(&app2.env()); - } - }); + tauri::api::dialog::ask( + app.get_window("core").as_ref(), + "Clear Config", + msg, + move |is_ok| { + if is_ok { + fs::remove_dir_all(root).unwrap(); + tauri::api::process::restart(&app2.env()); + } + }, + ); } pub fn merge(v: &Value, fields: &HashMap) -> Value { @@ -104,7 +113,10 @@ pub fn gen_cmd(name: String) -> String { re.replace_all(&name, "_").to_lowercase() } -pub async fn get_data(url: &str, app: Option<&tauri::AppHandle>) -> Result, reqwest::Error> { +pub async fn get_data( + url: &str, + app: Option<&tauri::AppHandle>, +) -> Result, reqwest::Error> { let res = reqwest::get(url).await?; let is_ok = res.status() == 200; let body = res.text().await?; @@ -222,7 +234,11 @@ pub async fn silent_install(app: AppHandle, update: UpdateResponse) -> } pub fn is_hidden(entry: &walkdir::DirEntry) -> bool { - entry.file_name().to_str().map(|s| s.starts_with('.')).unwrap_or(false) + entry + .file_name() + .to_str() + .map(|s| s.starts_with('.')) + .unwrap_or(false) } pub fn vec_to_hashmap( From 1e5ec6028d98162eeef49d3c532ad7341adc4c7c Mon Sep 17 00:00:00 2001 From: lencx Date: Sat, 21 Jan 2023 22:51:16 +0800 Subject: [PATCH 09/30] chore: fmt --- src-tauri/src/app/cmd.rs | 10 ++++---- src-tauri/src/app/menu.rs | 14 ++++++------ src-tauri/src/conf.rs | 48 +++++++++++++++++++-------------------- 3 files changed, 36 insertions(+), 36 deletions(-) diff --git a/src-tauri/src/app/cmd.rs b/src-tauri/src/app/cmd.rs index 4b87e4f..2127af8 100644 --- a/src-tauri/src/app/cmd.rs +++ b/src-tauri/src/app/cmd.rs @@ -299,8 +299,8 @@ pub async fn sync_prompts(app: AppHandle, time: u64) -> Option> fs::write( &model, serde_json::json!({ - "name": "ChatGPT Model", - "link": "https://github.com/lencx/ChatGPT" + "name": "ChatGPT Model", + "link": "https://github.com/lencx/ChatGPT" }) .to_string(), ) @@ -319,9 +319,9 @@ pub async fn sync_prompts(app: AppHandle, time: u64) -> Option> fs::write( model_cmd, serde_json::to_string_pretty(&serde_json::json!({ - "name": "ChatGPT CMD", - "last_updated": time, - "data": cmd_data, + "name": "ChatGPT CMD", + "last_updated": time, + "data": cmd_data, })) .unwrap(), ) diff --git a/src-tauri/src/app/menu.rs b/src-tauri/src/app/menu.rs index 9d7d440..4860e6c 100644 --- a/src-tauri/src/app/menu.rs +++ b/src-tauri/src/app/menu.rs @@ -348,18 +348,18 @@ pub fn menu_handler(event: WindowMenuEvent) { "scroll_top" => win .eval( r#"window.scroll({ - top: 0, - left: 0, - behavior: "smooth" - })"#, + top: 0, + left: 0, + behavior: "smooth" + })"#, ) .unwrap(), "scroll_bottom" => win .eval( r#"window.scroll({ - top: document.body.scrollHeight, - left: 0, - behavior: "smooth"})"#, + top: document.body.scrollHeight, + left: 0, + behavior: "smooth"})"#, ) .unwrap(), // Help diff --git a/src-tauri/src/conf.rs b/src-tauri/src/conf.rs index b70d68c..9f6c648 100644 --- a/src-tauri/src/conf.rs +++ b/src-tauri/src/conf.rs @@ -18,32 +18,32 @@ 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 DEFAULT_CHAT_CONF: &str = r#"{ - "stay_on_top": false, - "auto_update": "Prompt", - "theme": "Light", - "tray": true, - "titlebar": true, - "popup_search": false, - "global_shortcut": "", - "hide_dock_icon": false, - "default_origin": "https://chat.openai.com", - "origin": "https://chat.openai.com", - "ua_window": "", - "ua_tray": "" + "stay_on_top": false, + "auto_update": "Prompt", + "theme": "Light", + "tray": true, + "titlebar": true, + "popup_search": false, + "global_shortcut": "", + "hide_dock_icon": false, + "default_origin": "https://chat.openai.com", + "origin": "https://chat.openai.com", + "ua_window": "", + "ua_tray": "" }"#; pub const DEFAULT_CHAT_CONF_MAC: &str = r#"{ - "stay_on_top": false, - "auto_update": "Prompt", - "theme": "Light", - "tray": true, - "titlebar": false, - "popup_search": false, - "global_shortcut": "", - "hide_dock_icon": false, - "default_origin": "https://chat.openai.com", - "origin": "https://chat.openai.com", - "ua_window": "", - "ua_tray": "" + "stay_on_top": false, + "auto_update": "Prompt", + "theme": "Light", + "tray": true, + "titlebar": false, + "popup_search": false, + "global_shortcut": "", + "hide_dock_icon": false, + "default_origin": "https://chat.openai.com", + "origin": "https://chat.openai.com", + "ua_window": "", + "ua_tray": "" }"#; #[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] From b8757277539546ffcfe60047241dc33b69ed83d9 Mon Sep 17 00:00:00 2001 From: lencx Date: Sun, 22 Jan 2023 11:43:56 +0800 Subject: [PATCH 10/30] chore: settings --- src/routes.tsx | 8 +- src/view/General.tsx | 176 ------------------------------- src/view/settings/General.tsx | 82 ++++++++++++++ src/view/settings/MainWindow.tsx | 53 ++++++++++ src/view/settings/TrayWindow.tsx | 16 +++ src/view/settings/index.tsx | 85 +++++++++++++++ 6 files changed, 240 insertions(+), 180 deletions(-) delete mode 100644 src/view/General.tsx create mode 100644 src/view/settings/General.tsx create mode 100644 src/view/settings/MainWindow.tsx create mode 100644 src/view/settings/TrayWindow.tsx create mode 100644 src/view/settings/index.tsx diff --git a/src/routes.tsx b/src/routes.tsx index 5e17488..e38d38a 100644 --- a/src/routes.tsx +++ b/src/routes.tsx @@ -11,7 +11,7 @@ import { } from '@ant-design/icons'; import type { MenuProps } from 'antd'; -import General from '@/view/General'; +import Settings from '@/view/settings'; import Awesome from '@/view/awesome'; import UserCustom from '@/view/model/UserCustom'; import SyncPrompts from '@/view/model/SyncPrompts'; @@ -104,10 +104,10 @@ export const routes: Array = [ }, }, { - path: '/general', - element: , + path: '/settings', + element: , meta: { - label: 'General', + label: 'Settings', icon: , }, }, diff --git a/src/view/General.tsx b/src/view/General.tsx deleted file mode 100644 index 7e709ad..0000000 --- a/src/view/General.tsx +++ /dev/null @@ -1,176 +0,0 @@ -import { useEffect, useState } from 'react'; -import { Form, Radio, Switch, Input, Button, Space, message, Tooltip } from 'antd'; -import { QuestionCircleOutlined } from '@ant-design/icons'; -import { invoke } from '@tauri-apps/api'; -import { platform } from '@tauri-apps/api/os'; -import { ask } from '@tauri-apps/api/dialog'; -import { relaunch } from '@tauri-apps/api/process'; -import { clone, omit, isEqual } from 'lodash'; - -import useInit from '@/hooks/useInit'; -import FilePath from '@/components/FilePath'; -import { DISABLE_AUTO_COMPLETE, CHAT_CONF_JSON } from '@/utils'; - -const AutoUpdateLabel = () => { - return ( - - Auto Update -
Auto Update Policy
- Prompt: prompt to install
- Silent: install silently
- {/*Disable: disable auto update
*/} - - )}>
-
- ) -} - -const OriginLabel = ({ url }: { url: string }) => { - return ( - - Switch Origin - - ) -} - -const PopupSearchLabel = () => { - return ( - - Pop-up Search - {' '} - -
Generate images according to the content: Select the ChatGPT content with the mouse, no more than 400 characters. the DALL·E 2 button appears, and click to jump (Note: because the search content filled by the script cannot trigger the event directly, you need to enter a space in the input box to make the button clickable).
-
The application is built using Tauri, and due to its security restrictions, some of the action buttons will not work, so we recommend going to your browser.
- - )}>
-
- ) -} - -const GlobalShortcutLabel = () => { - return ( -
- Global Shortcut - {' '} - -
Shortcut definition, modifiers and key separated by "+" e.g. CmdOrControl+Q
-
If empty, the shortcut is disabled.
- https://tauri.app/v1/api/js/globalshortcut -
- )}> - - - - ) -} - -export default function General() { - const [form] = Form.useForm(); - const [platformInfo, setPlatform] = useState(''); - const [chatConf, setChatConf] = useState(null); - - useInit(async () => { - setPlatform(await platform()); - const chatData = await invoke('get_chat_conf'); - setChatConf(chatData); - }); - - useEffect(() => { - form.setFieldsValue(clone(chatConf)); - }, [chatConf]) - - const onCancel = () => { - form.setFieldsValue(chatConf); - }; - - const onReset = async () => { - const chatData = await invoke('reset_chat_conf'); - setChatConf(chatData); - const isOk = await ask(`Configuration reset successfully, whether to restart?`, { - title: 'ChatGPT Preferences' - }); - if (isOk) { - relaunch(); - return; - } - message.success('Configuration reset successfully'); - }; - - const onFinish = async (values: any) => { - if (!isEqual(omit(chatConf, ['default_origin']), values)) { - await invoke('form_confirm', { data: values, label: 'main' }); - const isOk = await ask(`Configuration saved successfully, whether to restart?`, { - title: 'ChatGPT Preferences' - }); - if (isOk) { - relaunch(); - return; - } - message.success('Configuration saved successfully'); - } - }; - - return ( - <> - -
- - - - {platformInfo === 'darwin' && ( - - - - )} - } name="popup_search" valuePropName="checked"> - - - - - Light - Dark - {["darwin", "windows"].includes(platformInfo) && ( - System - )} - - - } name="auto_update"> - - Prompt - Silent - {/*Disable*/} - - - } name="global_shortcut"> - - - } name="origin"> - - - - - - - - - - - - - - - - - - - ) -} \ No newline at end of file diff --git a/src/view/settings/General.tsx b/src/view/settings/General.tsx new file mode 100644 index 0000000..9b2189d --- /dev/null +++ b/src/view/settings/General.tsx @@ -0,0 +1,82 @@ +import { useState } from 'react'; +import { Form, Radio, Switch, Input, Tooltip } from 'antd'; +import { QuestionCircleOutlined } from '@ant-design/icons'; +import { platform } from '@tauri-apps/api/os'; + +import useInit from '@/hooks/useInit'; +import { DISABLE_AUTO_COMPLETE } from '@/utils'; + +const AutoUpdateLabel = () => { + return ( + + Auto Update + {' '} + +
Auto Update Policy
+
Prompt: prompt to install
+
Silent: install silently
+ {/*
Disable: disable auto update
*/} + + )}>
+
+ ) +} + +const GlobalShortcutLabel = () => { + return ( +
+ Global Shortcut + {' '} + +
Shortcut definition, modifiers and key separated by "+" e.g. CmdOrControl+Q
+
If empty, the shortcut is disabled.
+ https://tauri.app/v1/api/js/globalshortcut +
+ )}> + + + + ) +} + +export default function General() { + const [platformInfo, setPlatform] = useState(''); + + useInit(async () => { + setPlatform(await platform()); + }); + + return ( + <> + + + + {platformInfo === 'darwin' && ( + + + + )} + + + Light + Dark + {["darwin", "windows"].includes(platformInfo) && ( + System + )} + + + } name="auto_update"> + + Prompt + Silent + {/*Disable*/} + + + } name="global_shortcut"> + + + + ) +} diff --git a/src/view/settings/MainWindow.tsx b/src/view/settings/MainWindow.tsx new file mode 100644 index 0000000..d5e511e --- /dev/null +++ b/src/view/settings/MainWindow.tsx @@ -0,0 +1,53 @@ +import { useState } from 'react'; +import { Form, Switch, Input, Tooltip } from 'antd'; +import { QuestionCircleOutlined } from '@ant-design/icons'; +import { invoke } from '@tauri-apps/api'; + +import useInit from '@/hooks/useInit'; +import { DISABLE_AUTO_COMPLETE } from '@/utils'; + +const OriginLabel = ({ url }: { url: string }) => { + return ( + + Switch Origin + + ) +} + +const PopupSearchLabel = () => { + return ( + + Pop-up Search + {' '} + +
Generate images according to the content: Select the ChatGPT content with the mouse, no more than 400 characters. the DALL·E 2 button appears, and click to jump (Note: because the search content filled by the script cannot trigger the event directly, you need to enter a space in the input box to make the button clickable).
+
The application is built using Tauri, and due to its security restrictions, some of the action buttons will not work, so we recommend going to your browser.
+ + )}>
+
+ ) +} + +export default function General() { + const [chatConf, setChatConf] = useState(null); + + useInit(async () => { + const chatData = await invoke('get_chat_conf'); + setChatConf(chatData); + }); + + return ( + <> + } name="popup_search" valuePropName="checked"> + + + } name="origin"> + + + + + + + ) +} diff --git a/src/view/settings/TrayWindow.tsx b/src/view/settings/TrayWindow.tsx new file mode 100644 index 0000000..7ee94f1 --- /dev/null +++ b/src/view/settings/TrayWindow.tsx @@ -0,0 +1,16 @@ +import { Form, Switch, Input } from 'antd'; + +import { DISABLE_AUTO_COMPLETE } from '@/utils'; + +export default function General() { + return ( + <> + + + + + + + + ) +} diff --git a/src/view/settings/index.tsx b/src/view/settings/index.tsx new file mode 100644 index 0000000..a6a442a --- /dev/null +++ b/src/view/settings/index.tsx @@ -0,0 +1,85 @@ +import { useEffect, useState } from 'react'; +import { Form, Tabs, Space, Button, message } from 'antd'; +import { invoke, dialog, process } from '@tauri-apps/api'; +import { clone, omit, isEqual } from 'lodash'; + +import useInit from '@/hooks/useInit'; +import FilePath from '@/components/FilePath'; +import { CHAT_CONF_JSON } from '@/utils'; +import General from './General'; +import MainWindow from './MainWindow'; +import TrayWindow from './TrayWindow'; + +export default function Settings() { + const [form] = Form.useForm(); + const [chatConf, setChatConf] = useState(null); + + useInit(async () => { + const chatData = await invoke('get_chat_conf'); + setChatConf(chatData); + }); + + useEffect(() => { + form.setFieldsValue(clone(chatConf)); + }, [chatConf]) + + const onCancel = () => { + form.setFieldsValue(chatConf); + }; + + const onReset = async () => { + const chatData = await invoke('reset_chat_conf'); + setChatConf(chatData); + const isOk = await dialog.ask(`Configuration reset successfully, whether to restart?`, { + title: 'ChatGPT Preferences' + }); + if (isOk) { + process.relaunch(); + return; + } + message.success('Configuration reset successfully'); + }; + + const onFinish = async (values: any) => { + if (!isEqual(omit(chatConf, ['default_origin']), values)) { + await invoke('form_confirm', { data: values, label: 'main' }); + const isOk = await dialog.ask(`Configuration saved successfully, whether to restart?`, { + title: 'ChatGPT Preferences' + }); + if (isOk) { + process.relaunch(); + return; + } + message.success('Configuration saved successfully'); + } + }; + + return ( +
+ +
+ }, + { label: 'Main Window', key: 'main_window', children: }, + { label: 'SystemTray Window', key: 'tray_window', children: }, + ]} + /> + + + + + + + + + +
+ ) +} \ No newline at end of file From 1d7bb3e0515c11abec1973e1610a84ee50d1acab Mon Sep 17 00:00:00 2001 From: lencx Date: Sun, 22 Jan 2023 14:46:04 +0800 Subject: [PATCH 11/30] chore: add lint --- .github/workflows/clippy.yml | 14 ++++++++++++ .github/workflows/lint.yml | 31 ++++++++++++++++++++++++++ Cargo.toml | 9 ++++++++ src-tauri/rustfmt.toml => rustfmt.toml | 0 src-tauri/Cargo.toml | 7 ------ src-tauri/src/app/window.rs | 5 +---- src-tauri/src/utils.rs | 9 +++++++- 7 files changed, 63 insertions(+), 12 deletions(-) create mode 100644 .github/workflows/clippy.yml create mode 100644 .github/workflows/lint.yml create mode 100644 Cargo.toml rename src-tauri/rustfmt.toml => rustfmt.toml (100%) diff --git a/.github/workflows/clippy.yml b/.github/workflows/clippy.yml new file mode 100644 index 0000000..d190465 --- /dev/null +++ b/.github/workflows/clippy.yml @@ -0,0 +1,14 @@ +on: push +name: Clippy check + +# Make sure CI fails on all warnings, including Clippy lints +env: + RUSTFLAGS: "-Dwarnings" + +jobs: + clippy_check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Run Clippy + run: cargo clippy --all-targets --all-features \ No newline at end of file diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..9005581 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,31 @@ +name: Lint CI +on: [push, pull_request] + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - name: Setup | Checkout + uses: actions/checkout@v3 + + - name: Setup | Ubuntu dependencies + run: sudo apt install libasound2-dev libudev-dev pkg-config + + - name: Setup | Toolchain (clippy, rustfmt) + uses: dtolnay/rust-toolchain@stable + with: + components: clippy, rustfmt + + - uses: Swatinem/rust-cache@v2 + + - name: Lint | Clippy + - uses: actions-rs/cargo@v1 + with: + command: clippy + args: --all-targets --all-features -- -D warnings + + - name: Lint | Rustfmt + - uses: actions-rs/cargo@v1 + with: + command: fmt + args: --all -- --check \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..47083f7 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,9 @@ +[workspace] +members = ["src-tauri"] + +# fix: mac v1.2.0 can not copy/paste +# https://github.com/tauri-apps/tauri/issues/5669 +[profile.release] +strip = true +lto = true +opt-level = "s" diff --git a/src-tauri/rustfmt.toml b/rustfmt.toml similarity index 100% rename from src-tauri/rustfmt.toml rename to rustfmt.toml diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 3d2132f..cbf996f 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -40,10 +40,3 @@ default = [ "custom-protocol" ] # this feature is used used for production builds where `devPath` points to the filesystem # DO NOT remove this custom-protocol = [ "tauri/custom-protocol" ] - -# fix: mac v1.2.0 can not copy/paste -# https://github.com/tauri-apps/tauri/issues/5669 -[profile.release] -strip = true -lto = true -opt-level = "s" diff --git a/src-tauri/src/app/window.rs b/src-tauri/src/app/window.rs index 1b34874..990e7b3 100644 --- a/src-tauri/src/app/window.rs +++ b/src-tauri/src/app/window.rs @@ -42,10 +42,7 @@ pub fn dalle2_window( let app = handle.clone(); let query = if query.is_some() { - format!( - "window.addEventListener('DOMContentLoaded', function() {{\nwindow.__CHATGPT_QUERY__='{}';\n}})", - query.unwrap() - ) + format!("window.addEventListener('DOMContentLoaded', function() {{\nwindow.__CHATGPT_QUERY__='{}';\n}})", query.unwrap()) } else { "".to_string() }; diff --git a/src-tauri/src/utils.rs b/src-tauri/src/utils.rs index 63cd7dd..d0735c9 100644 --- a/src-tauri/src/utils.rs +++ b/src-tauri/src/utils.rs @@ -44,7 +44,14 @@ pub fn script_path() -> PathBuf { let script_file = chat_root().join("main.js"); if !exists(&script_file) { create_file(&script_file).unwrap(); - fs::write(&script_file, format!("// *** ChatGPT User Script ***\n// @github: https://github.com/lencx/ChatGPT \n// @path: {}\n\nconsole.log('🤩 Hello ChatGPT!!!');", &script_file.to_string_lossy())).unwrap(); + 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 From c5086332627a0d581472e9832d89c0468d504e15 Mon Sep 17 00:00:00 2001 From: lencx Date: Sun, 22 Jan 2023 14:48:01 +0800 Subject: [PATCH 12/30] chore: add lint --- .github/workflows/clippy.yml | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 .github/workflows/clippy.yml diff --git a/.github/workflows/clippy.yml b/.github/workflows/clippy.yml deleted file mode 100644 index d190465..0000000 --- a/.github/workflows/clippy.yml +++ /dev/null @@ -1,14 +0,0 @@ -on: push -name: Clippy check - -# Make sure CI fails on all warnings, including Clippy lints -env: - RUSTFLAGS: "-Dwarnings" - -jobs: - clippy_check: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: Run Clippy - run: cargo clippy --all-targets --all-features \ No newline at end of file From bc39dcdd7294bc95a43a79d2288bed5e08824adc Mon Sep 17 00:00:00 2001 From: lencx Date: Sun, 22 Jan 2023 18:18:36 +0800 Subject: [PATCH 13/30] chore: add pretty --- .github/ISSUE_TEMPLATE/bug_report.yml | 82 +++++++++---------- .github/ISSUE_TEMPLATE/build_error_report.yml | 70 ++++++++-------- .github/ISSUE_TEMPLATE/docmentation_issue.yml | 34 ++++---- .github/ISSUE_TEMPLATE/feature_request.yml | 64 +++++++-------- .github/ISSUE_TEMPLATE/security.yml | 64 +++++++-------- .github/workflows/lint.yml | 2 +- .prettierignore | 45 ++++++++++ .prettierrc | 7 ++ README-ZH_CN.md | 2 + README.md | 3 + UPDATE_LOG.md | 20 +++++ package.json | 13 ++- scripts/download.js | 2 +- src/components/FilePath/index.tsx | 17 ++-- src/components/Markdown/Editor.tsx | 18 ++-- src/components/Markdown/index.scss | 7 +- src/components/Markdown/index.tsx | 15 ++-- src/components/Tags/index.tsx | 4 +- src/hooks/useChatModel.ts | 14 ++-- src/hooks/useColumns.tsx | 32 ++++---- src/hooks/useData.ts | 10 +-- src/hooks/useInit.ts | 4 +- src/hooks/useTable.tsx | 17 ++-- src/icons/SplitIcon.tsx | 26 ++++-- src/layout/index.scss | 2 +- src/layout/index.tsx | 40 +++++---- src/main.scss | 5 +- src/main.tsx | 4 +- src/routes.tsx | 10 +-- src/utils.ts | 60 +++++++++----- src/view/awesome/Form.tsx | 12 +-- src/view/awesome/config.tsx | 14 ++-- src/view/awesome/index.tsx | 54 ++++++------ src/view/download/config.tsx | 18 ++-- src/view/download/index.tsx | 24 ++++-- src/view/markdown/index.scss | 3 +- src/view/markdown/index.tsx | 12 ++- src/view/model/SyncCustom/Form.tsx | 52 ++++++++---- src/view/model/SyncCustom/config.tsx | 26 +++--- src/view/model/SyncCustom/index.tsx | 58 +++++++------ src/view/model/SyncPrompts/config.tsx | 4 +- src/view/model/SyncPrompts/index.scss | 3 +- src/view/model/SyncPrompts/index.tsx | 20 +++-- src/view/model/SyncRecord/config.tsx | 10 ++- src/view/model/SyncRecord/index.tsx | 22 +++-- src/view/model/UserCustom/Form.tsx | 17 ++-- src/view/model/UserCustom/config.tsx | 14 ++-- src/view/model/UserCustom/index.tsx | 77 ++++++++++------- src/view/notes/config.tsx | 18 ++-- src/view/notes/index.tsx | 10 +-- src/view/settings/General.tsx | 62 ++++++++------ src/view/settings/MainWindow.tsx | 48 +++++++---- src/view/settings/TrayWindow.tsx | 8 +- src/view/settings/index.tsx | 18 ++-- tsconfig.json | 2 +- vite.config.ts | 12 +-- 56 files changed, 776 insertions(+), 535 deletions(-) create mode 100644 .prettierignore create mode 100644 .prettierrc diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 21688e9..cebefb1 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -1,43 +1,43 @@ -name: "🕷️ Bug report" -description: "report bugs" -title: "[Bug]" +name: '🕷️ Bug report' +description: 'report bugs' +title: '[Bug]' labels: - - "bug" + - 'bug' body: -- type: markdown - attributes: - value: "Please make sure to [search for existing issues](https://github.com/lencx/ChatGPT/issues) before filing a new one!" -- type: markdown - attributes: - value: | - ## Bug report - Please fill in the following information to help us reproduce the bug: -- type: input - id: version - attributes: - label: Version - description: "Please specify the version of ChatGPT you are using, a newer version may have fixed the bug you encountered.Check the [UPDATE_LOG](https://github.com/lencx/ChatGPT/blob/main/UPDATE_LOG.md) for more information." - placeholder: "e.g. v0.1.0" - validations: - required: true -- type: textarea - id: bug - attributes: - label: Bug description - description: | - Please describe the bug here,if possible, please provide a minimal example to reproduce the bug.The content of `~/.chatgpt/chatgpt.log` may be helpful if you encounter a crash. - validations: - required: true -- type: input - id: OS - attributes: - label: OS - description: "Please specify the OS you are using." - placeholder: "e.g. Ubuntu 22.04" - validations: - required: true -- type: textarea - id: environment - attributes: - label: Environment - description: "If you think your environment may be related to the problem, please describe it here." + - type: markdown + attributes: + value: 'Please make sure to [search for existing issues](https://github.com/lencx/ChatGPT/issues) before filing a new one!' + - type: markdown + attributes: + value: | + ## Bug report + Please fill in the following information to help us reproduce the bug: + - type: input + id: version + attributes: + label: Version + description: 'Please specify the version of ChatGPT you are using, a newer version may have fixed the bug you encountered.Check the [UPDATE_LOG](https://github.com/lencx/ChatGPT/blob/main/UPDATE_LOG.md) for more information.' + placeholder: 'e.g. v0.1.0' + validations: + required: true + - type: textarea + id: bug + attributes: + label: Bug description + description: | + Please describe the bug here,if possible, please provide a minimal example to reproduce the bug.The content of `~/.chatgpt/chatgpt.log` may be helpful if you encounter a crash. + validations: + required: true + - type: input + id: OS + attributes: + label: OS + description: 'Please specify the OS you are using.' + placeholder: 'e.g. Ubuntu 22.04' + validations: + required: true + - type: textarea + id: environment + attributes: + label: Environment + description: 'If you think your environment may be related to the problem, please describe it here.' diff --git a/.github/ISSUE_TEMPLATE/build_error_report.yml b/.github/ISSUE_TEMPLATE/build_error_report.yml index 7223b20..a2db2fc 100644 --- a/.github/ISSUE_TEMPLATE/build_error_report.yml +++ b/.github/ISSUE_TEMPLATE/build_error_report.yml @@ -1,37 +1,37 @@ -name: "❌ Build error report" -description: "report errors when building by yourself" -title: "[Build Error]" +name: '❌ Build error report' +description: 'report errors when building by yourself' +title: '[Build Error]' labels: - - "build error" + - 'build error' body: -- type: markdown - attributes: - value: "Please make sure to [search for existing issues](https://github.com/lencx/ChatGPT/issues) before filing a new one!" -- type: markdown - attributes: - value: "Please make sure to build from the source code with the latest version of ChatGPT." -- type: markdown - attributes: - value: | - ## Build error report - Please fill in the following information to help us reproduce the bug: -- type: textarea - id: error - attributes: - label: Error message - description: "Please paste the error message here." - validations: - required: true -- type: input - id: OS - attributes: - label: OS - description: "Please specify the OS you are using." - placeholder: "e.g. Ubuntu 22.04" - validations: - required: true -- type: textarea - id: environment - attributes: - label: Environment - description: "If you think your environment may be related to the problem, please describe it here." \ No newline at end of file + - type: markdown + attributes: + value: 'Please make sure to [search for existing issues](https://github.com/lencx/ChatGPT/issues) before filing a new one!' + - type: markdown + attributes: + value: 'Please make sure to build from the source code with the latest version of ChatGPT.' + - type: markdown + attributes: + value: | + ## Build error report + Please fill in the following information to help us reproduce the bug: + - type: textarea + id: error + attributes: + label: Error message + description: 'Please paste the error message here.' + validations: + required: true + - type: input + id: OS + attributes: + label: OS + description: 'Please specify the OS you are using.' + placeholder: 'e.g. Ubuntu 22.04' + validations: + required: true + - type: textarea + id: environment + attributes: + label: Environment + description: 'If you think your environment may be related to the problem, please describe it here.' diff --git a/.github/ISSUE_TEMPLATE/docmentation_issue.yml b/.github/ISSUE_TEMPLATE/docmentation_issue.yml index 6aff586..fc64dbd 100644 --- a/.github/ISSUE_TEMPLATE/docmentation_issue.yml +++ b/.github/ISSUE_TEMPLATE/docmentation_issue.yml @@ -1,19 +1,19 @@ -name: "📚 Documentation Issue" -description: "report documentation issues, typos welcome!" -title: "[Doc]" +name: '📚 Documentation Issue' +description: 'report documentation issues, typos welcome!' +title: '[Doc]' labels: - - "documentation" + - 'documentation' body: -- type: markdown - attributes: - value: "Please make sure to [search for existing issues](https://github.com/lencx/ChatGPT/issues) before creating a new one." -- type: textarea - id: doc-description - attributes: - label: "Provide a description of requested docs changes" - description: "Briefly describe the requested docs changes." - validations: - required: true -- type: markdown - attributes: - value: Please limit one request per issue. \ No newline at end of file + - type: markdown + attributes: + value: 'Please make sure to [search for existing issues](https://github.com/lencx/ChatGPT/issues) before creating a new one.' + - type: textarea + id: doc-description + attributes: + label: 'Provide a description of requested docs changes' + description: 'Briefly describe the requested docs changes.' + validations: + required: true + - type: markdown + attributes: + value: Please limit one request per issue. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 75eca7e..c1f82ed 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -1,34 +1,34 @@ -name: "⭐ Feature or enhancement request" -description: "suggest new features or enhancements" -title: "[Feature]" +name: '⭐ Feature or enhancement request' +description: 'suggest new features or enhancements' +title: '[Feature]' labels: - - "enhancement" + - 'enhancement' body: -- type: markdown - attributes: - value: "Please make sure to [search for existing issues](https://github.com/lencx/ChatGPT/issues) before creating a new one." -- type: textarea - id: feature-description - attributes: - label: "Feature description" - description: "Describe the feature or enhancements you'd like to see." - validations: - required: true -- type: textarea - id: motivation - attributes: - label: "Motivation" - description: "Describe the motivation for this feature or enhancement." -- type: textarea - id: alternatives - attributes: - label: "Alternatives" - description: "Describe any alternatives you've considered." -- type: textarea - id: additional-context - attributes: - label: "Additional context" - description: "Add any other context or screenshots about the feature request here." -- type: markdown - attributes: - value: Please limit one request per issue. + - type: markdown + attributes: + value: 'Please make sure to [search for existing issues](https://github.com/lencx/ChatGPT/issues) before creating a new one.' + - type: textarea + id: feature-description + attributes: + label: 'Feature description' + description: "Describe the feature or enhancements you'd like to see." + validations: + required: true + - type: textarea + id: motivation + attributes: + label: 'Motivation' + description: 'Describe the motivation for this feature or enhancement.' + - type: textarea + id: alternatives + attributes: + label: 'Alternatives' + description: "Describe any alternatives you've considered." + - type: textarea + id: additional-context + attributes: + label: 'Additional context' + description: 'Add any other context or screenshots about the feature request here.' + - type: markdown + attributes: + value: Please limit one request per issue. diff --git a/.github/ISSUE_TEMPLATE/security.yml b/.github/ISSUE_TEMPLATE/security.yml index 7c0653b..0bb5b56 100644 --- a/.github/ISSUE_TEMPLATE/security.yml +++ b/.github/ISSUE_TEMPLATE/security.yml @@ -1,34 +1,34 @@ -name: "⚠️ Security&Privacy issue" -description: "Report security or privacy issues" -title: "[Security]" +name: '⚠️ Security&Privacy issue' +description: 'Report security or privacy issues' +title: '[Security]' labels: - - "security" + - 'security' body: -- type: markdown - attributes: - value: "Please make sure to [search for existing issues](https://github.com/lencx/ChatGPT/issues) before creating a new one." -- type: textarea - id: security-description - attributes: - label: "Description" - description: "Describe the security or privacy issue." - validations: - required: true -- type: textarea - id: motivation - attributes: - label: "Motivation" - description: "Describe the motivation for this security or privacy issue." -- type: textarea - id: alternatives - attributes: - label: "Alternatives" - description: "Describe any alternatives you've considered." -- type: textarea - id: additional-context - attributes: - label: "Additional context" - description: "Add any other context or screenshots about the security or privacy issue here." -- type: markdown - attributes: - value: Please limit one request per issue. \ No newline at end of file + - type: markdown + attributes: + value: 'Please make sure to [search for existing issues](https://github.com/lencx/ChatGPT/issues) before creating a new one.' + - type: textarea + id: security-description + attributes: + label: 'Description' + description: 'Describe the security or privacy issue.' + validations: + required: true + - type: textarea + id: motivation + attributes: + label: 'Motivation' + description: 'Describe the motivation for this security or privacy issue.' + - type: textarea + id: alternatives + attributes: + label: 'Alternatives' + description: "Describe any alternatives you've considered." + - type: textarea + id: additional-context + attributes: + label: 'Additional context' + description: 'Add any other context or screenshots about the security or privacy issue here.' + - type: markdown + attributes: + value: Please limit one request per issue. diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 9005581..28d01d5 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -28,4 +28,4 @@ jobs: - uses: actions-rs/cargo@v1 with: command: fmt - args: --all -- --check \ No newline at end of file + args: --all -- --check diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..c61b10f --- /dev/null +++ b/.prettierignore @@ -0,0 +1,45 @@ +package-lock.json +node_modules/ +yarn.lock +*.lock + +casks/ + +# rust +src-tauri/ +target/ +Cargo.lock +*.toml + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +assets/** +public/** + +.gitattributes +.gitignore +.prettierignore + +LICENSE \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..c36a279 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,7 @@ +{ + "trailingComma": "all", + "singleQuote": true, + "semi": true, + "tabWidth": 2, + "printWidth": 100 +} diff --git a/README-ZH_CN.md b/README-ZH_CN.md index f58c2c2..42b149a 100644 --- a/README-ZH_CN.md +++ b/README-ZH_CN.md @@ -10,6 +10,7 @@ [![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_) + Buy Me A Coffee @@ -25,6 +26,7 @@ - [ChatGPT_0.9.2_x64_en-US.msi](https://github.com/lencx/ChatGPT/releases/download/v0.9.2/ChatGPT_0.9.2_x64_en-US.msi): - 使用 [winget](https://winstall.app/apps/lencx.ChatGPT): + ```bash # install the latest version winget install --id=lencx.ChatGPT -e diff --git a/README.md b/README.md index 7bf5fb1..2f008f6 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ [![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_) + @@ -27,6 +28,7 @@ - [ChatGPT_0.9.2_x64_en-US.msi](https://github.com/lencx/ChatGPT/releases/download/v0.9.2/ChatGPT_0.9.2_x64_en-US.msi): Direct download installer - Use [winget](https://winstall.app/apps/lencx.ChatGPT): + ```bash # install the latest version winget install --id=lencx.ChatGPT -e @@ -174,6 +176,7 @@ Currently, only json and csv are supported for synchronizing custom files, and t ## 📌 TODO + - `Control Center` enhancement - `Pop-up Search` enhancement - ... diff --git a/UPDATE_LOG.md b/UPDATE_LOG.md index fd708c1..191a5ce 100644 --- a/UPDATE_LOG.md +++ b/UPDATE_LOG.md @@ -11,15 +11,18 @@ fix: slash command does not work ## v0.9.0 fix: + - export button does not work feat: + - add an export markdown button - `Control Center` adds `Notes` and `Download` menus for managing exported chat files (Markdown, PNG, PDF). `Notes` supports markdown previews. ## v0.8.1 fix: + - export button keeps blinking - export button in the old chat does not work - disable export sharing links because it is a security risk @@ -27,22 +30,26 @@ fix: ## v0.8.0 feat: + - theme enhancement (Light, Dark, System) - automatic updates support `silent` settings - pop-up search: select the ChatGPT content with the mouse, the `DALL·E 2` button appears, and click to jump (note: because the search content filled by the script cannot trigger the event directly, you need to enter a space in the input box to make the button clickable). fix: + - close the main window and hide it in the tray (windows systems) ## v0.7.4 fix: + - trying to resolve linux errors: `error while loading shared libraries` - customize global shortcuts (`Menu -> Preferences -> Control Center -> General -> Global Shortcut`) ## v0.7.3 chore: + - optimize slash command style - optimize tray menu icon and button icons - global shortcuts to the chatgpt app (mac: `Command + Shift + O`, windows: `Ctrl + Shift + O`) @@ -54,6 +61,7 @@ fix: some windows systems cannot start the application ## v0.7.1 fix: + - some windows systems cannot start the application - windows and linux add about menu (show version information) - the tray icon is indistinguishable from the background in dark mode on window and linux @@ -61,10 +69,12 @@ fix: ## v0.7.0 fix: + - mac m1 copy/paste does not work on some system versions - optimize the save chat log button to a small icon, the tray window no longer provides a save chat log button (the buttons causes the input area to become larger and the content area to become smaller) feat: + - use the keyboard `⇧` (arrow up) and `⇩` (arrow down) keys to select the slash command @@ -77,6 +87,7 @@ fix: sync failure on windows fix: path not allowed on the configured scope feat: + - optimize the generated pdf file size - menu added `Sync Prompts` - `Control Center` added `Sync Custom` @@ -86,6 +97,7 @@ feat: ## v0.6.0 fix: + - windows show Chinese when upgrading ## v0.5.1 @@ -103,11 +115,13 @@ add chatgpt log (path: `~/.chatgpt/chatgpt.log`) ## v0.4.1 fix: + - tray window style optimization ## v0.4.0 feat: + - customize the ChatGPT prompts command (https://github.com/lencx/ChatGPT#-announcement) - menu enhancement: hide application icons from the Dock (support macOS only) @@ -116,12 +130,14 @@ feat: fix: can't open ChatGPT feat: menu enhancement + - the control center of ChatGPT application - open the configuration file directory ## v0.2.2 feat: + - menu: go to config ## v0.2.1 @@ -131,12 +147,14 @@ feat: menu optimization ## v0.2.0 feat: menu enhancement + - customize user-agent to prevent security detection interception - clear all chatgpt configuration files ## v0.1.8 feat: + - menu enhancement: theme, titlebar - modify website address @@ -147,6 +165,7 @@ feat: tray window ## v0.1.6 feat: + - stay on top - export ChatGPT history @@ -157,6 +176,7 @@ fix: mac can't use shortcut keys ## v0.1.4 feat: + - beautify icons - add system tray menu diff --git a/package.json b/package.json index de5c24d..8c52719 100644 --- a/package.json +++ b/package.json @@ -12,8 +12,11 @@ "fix:tray": "tr override --json.tauri_systemTray_iconPath=\"icons/tray-icon-light.png\" --json.tauri_systemTray_iconAsTemplate=false", "fix:tray:mac": "tr override --json.tauri_systemTray_iconPath=\"icons/tray-icon.png\" --json.tauri_systemTray_iconAsTemplate=true", "download": "node ./scripts/download.js", + "fmt:rs": "cargo fmt", "tr": "tr", - "tauri": "tauri" + "tauri": "tauri", + "prettier": "prettier -c --write '**/*'", + "pretty-quick": "pretty-quick" }, "license": "MIT", "author": "lencx ", @@ -32,6 +35,11 @@ "type": "git", "url": "https://github.com/lencx/ChatGPT" }, + "husky": { + "hooks": { + "pre-commit": "pretty-quick --staged" + } + }, "dependencies": { "@ant-design/icons": "^4.8.0", "@monaco-editor/react": "^4.4.6", @@ -59,6 +67,9 @@ "@types/react-syntax-highlighter": "^15.5.6", "@types/uuid": "^9.0.0", "@vitejs/plugin-react": "^3.0.0", + "husky": "^8.0.3", + "prettier": "^2.8.3", + "pretty-quick": "^3.1.3", "sass": "^1.56.2", "typescript": "^4.9.4", "vite": "^4.0.0", diff --git a/scripts/download.js b/scripts/download.js index 86a55fe..517ff8e 100644 --- a/scripts/download.js +++ b/scripts/download.js @@ -30,4 +30,4 @@ async function init() { rewrite('README-ZH_CN.md'); } -init().catch(console.error); \ No newline at end of file +init().catch(console.error); diff --git a/src/components/FilePath/index.tsx b/src/components/FilePath/index.tsx index 8976710..1b9cd09 100644 --- a/src/components/FilePath/index.tsx +++ b/src/components/FilePath/index.tsx @@ -22,15 +22,20 @@ const FilePath: FC = ({ className, label = 'PATH', paths = '', ur setPath(url); return; } - setPath(await path.join(await chatRoot(), ...paths.split('/').filter(i => !!i))); - })() - }, [url, paths]) + setPath(await path.join(await chatRoot(), ...paths.split('/').filter((i) => !!i))); + })(); + }, [url, paths]); return ( ); -} +}; -export default FilePath; \ No newline at end of file +export default FilePath; diff --git a/src/components/Markdown/Editor.tsx b/src/components/Markdown/Editor.tsx index 22fe339..8b36319 100644 --- a/src/components/Markdown/Editor.tsx +++ b/src/components/Markdown/Editor.tsx @@ -1,5 +1,5 @@ import { FC, useEffect, useState } from 'react'; -import Editor from "@monaco-editor/react"; +import Editor from '@monaco-editor/react'; import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels'; import Markdown from '@/components/Markdown'; @@ -17,12 +17,12 @@ const MarkdownEditor: FC = ({ value = '', onChange, mode = useEffect(() => { setContent(value); onChange && onChange(value); - }, [value]) + }, [value]); const handleEdit = (e: any) => { setContent(e); onChange && onChange(e); - } + }; const isSplit = mode === 'split'; @@ -31,11 +31,7 @@ const MarkdownEditor: FC = ({ value = '', onChange, mode = {['md', 'split'].includes(mode) && ( - + )} {isSplit && } @@ -44,9 +40,9 @@ const MarkdownEditor: FC = ({ value = '', onChange, mode = {content} )} - + - ) + ); }; -export default MarkdownEditor; \ No newline at end of file +export default MarkdownEditor; diff --git a/src/components/Markdown/index.scss b/src/components/Markdown/index.scss index 033bd36..5c0bc57 100644 --- a/src/components/Markdown/index.scss +++ b/src/components/Markdown/index.scss @@ -1,15 +1,16 @@ .markdown-body { height: 100%; overflow: auto; - font-family: -apple-system,BlinkMacSystemFont,"Segoe UI","Noto Sans",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji"; - + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, + sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji'; &.edit-preview { padding: 10px; font-size: 14px; } - pre, code { + pre, + code { font-family: monospace, monospace; } } diff --git a/src/components/Markdown/index.tsx b/src/components/Markdown/index.tsx index a548cc6..7277ebf 100644 --- a/src/components/Markdown/index.tsx +++ b/src/components/Markdown/index.tsx @@ -13,7 +13,6 @@ interface MarkdownProps { } const Markdown: FC = ({ children, className }) => { - return (
@@ -22,8 +21,8 @@ const Markdown: FC = ({ children, className }) => { linkTarget="_blank" remarkPlugins={[remarkGfm]} components={{ - code({node, inline, className, children, ...props}) { - const match = /language-(\w+)/.exec(className || '') + code({ node, inline, className, children, ...props }) { + const match = /language-(\w+)/.exec(className || ''); return !inline && match ? ( = ({ children, className }) => { {children} - ) - } + ); + }, }} />
- ) -} + ); +}; -export default Markdown; \ No newline at end of file +export default Markdown; diff --git a/src/components/Tags/index.tsx b/src/components/Tags/index.tsx index dd349e4..09218d9 100644 --- a/src/components/Tags/index.tsx +++ b/src/components/Tags/index.tsx @@ -20,7 +20,7 @@ const Tags: FC = ({ max = 99, value = [], onChange, addTxt = 'New Tag useEffect(() => { setTags(value); - }, [value]) + }, [value]); useEffect(() => { if (inputVisible) { @@ -97,4 +97,4 @@ const Tags: FC = ({ max = 99, value = [], onChange, addTxt = 'New Tag ); }; -export default Tags; \ No newline at end of file +export default Tags; diff --git a/src/hooks/useChatModel.ts b/src/hooks/useChatModel.ts index 980fe1c..837cce5 100644 --- a/src/hooks/useChatModel.ts +++ b/src/hooks/useChatModel.ts @@ -15,12 +15,12 @@ export default function useChatModel(key: string, file = CHAT_MODEL_JSON) { setModelJson(data); }); - const modelSet = async (data: Record[]|Record) => { + const modelSet = async (data: Record[] | Record) => { const oData = clone(modelJson); oData[key] = data; await writeJSON(file, oData); setModelJson(oData); - } + }; return { modelJson, modelSet, modelData: modelJson?.[key] || [] }; } @@ -40,15 +40,19 @@ export function useCacheModel(file = '') { await writeJSON(newFile ? newFile : file, data, { isRoot: true }); setModelCacheJson(data); await modelCacheCmd(); - } + }; const modelCacheCmd = async () => { // Generate the `chat.model.cmd.json` file and refresh the page for the slash command to take effect. const list = await invoke('cmd_list'); - await writeJSON(CHAT_MODEL_CMD_JSON, { name: 'ChatGPT CMD', last_updated: Date.now(), data: list }); + await writeJSON(CHAT_MODEL_CMD_JSON, { + name: 'ChatGPT CMD', + last_updated: Date.now(), + data: list, + }); await invoke('window_reload', { label: 'core' }); await invoke('window_reload', { label: 'tray' }); }; return { modelCacheJson, modelCacheSet, modelCacheCmd }; -} \ No newline at end of file +} diff --git a/src/hooks/useColumns.tsx b/src/hooks/useColumns.tsx index d5562ff..ab55762 100644 --- a/src/hooks/useColumns.tsx +++ b/src/hooks/useColumns.tsx @@ -5,7 +5,7 @@ import { DISABLE_AUTO_COMPLETE } from '@/utils'; export default function useColumns(columns: any[] = []) { const [opType, setOpType] = useState(''); - const [opRecord, setRecord] = useState | null>(null); + const [opRecord, setRecord] = useState | null>(null); const [opTime, setNow] = useState(null); const [opExtra, setExtra] = useState(null); @@ -58,26 +58,26 @@ export const EditRow: FC = ({ rowKey, row, actions }) => { setEdit(true); }; const handleChange = (e: React.ChangeEvent) => { - setVal(e.target.value) + setVal(e.target.value); }; const handleSave = () => { setEdit(false); row[rowKey] = val?.trim(); - actions?.setRecord(row, 'rowedit') + actions?.setRecord(row, 'rowedit'); }; - return isEdit - ? ( - - ) - : ( -
{val}
- ); + return isEdit ? ( + + ) : ( +
+ {val} +
+ ); }; diff --git a/src/hooks/useData.ts b/src/hooks/useData.ts index 86cf9fa..de82cce 100644 --- a/src/hooks/useData.ts +++ b/src/hooks/useData.ts @@ -8,7 +8,7 @@ export default function useData(oData: any[]) { useEffect(() => { opInit(oData); - }, []) + }, []); const opAdd = (val: any) => { const v = [val, ...opData]; @@ -18,19 +18,19 @@ export default function useData(oData: any[]) { const opInit = (val: any[] = []) => { if (!val || !Array.isArray(val)) return; - const nData = val.map(i => ({ [safeKey]: v4(), ...i })); + const nData = val.map((i) => ({ [safeKey]: v4(), ...i })); setData(nData); }; const opRemove = (id: string) => { - const nData = opData.filter(i => i[safeKey] !== id); + const nData = opData.filter((i) => i[safeKey] !== id); setData(nData); return nData; }; const opReplace = (id: string, data: any) => { const nData = [...opData]; - const idx = opData.findIndex(v => v[safeKey] === id); + const idx = opData.findIndex((v) => v[safeKey] === id); nData[idx] = data; setData(nData); return nData; @@ -52,4 +52,4 @@ export default function useData(oData: any[]) { }; return { opSafeKey: safeKey, opInit, opReplace, opAdd, opRemove, opData, opReplaceItems }; -} \ No newline at end of file +} diff --git a/src/hooks/useInit.ts b/src/hooks/useInit.ts index b5438ad..aa58b53 100644 --- a/src/hooks/useInit.ts +++ b/src/hooks/useInit.ts @@ -8,5 +8,5 @@ export default function useInit(callback: () => void) { callback(); isInit.current = false; } - }) -} \ No newline at end of file + }); +} diff --git a/src/hooks/useTable.tsx b/src/hooks/useTable.tsx index 4fc2c65..9469318 100644 --- a/src/hooks/useTable.tsx +++ b/src/hooks/useTable.tsx @@ -7,14 +7,17 @@ import { safeKey } from '@/hooks/useData'; type rowSelectionOptions = { key: 'id' | string; rowType: 'id' | 'row' | 'all'; -} +}; export function useTableRowSelection(options: Partial = {}) { const { key = 'id', rowType = 'id' } = options; const [selectedRowKeys, setSelectedRowKeys] = useState([]); const [selectedRowIDs, setSelectedRowIDs] = useState([]); - const [selectedRows, setSelectedRows] = useState[]>([]); + const [selectedRows, setSelectedRows] = useState[]>([]); - const onSelectChange = (newSelectedRowKeys: React.Key[], newSelectedRows: Record[]) => { + const onSelectChange = ( + newSelectedRowKeys: React.Key[], + newSelectedRows: Record[], + ) => { const keys = newSelectedRows.map((i: any) => i[safeKey] || i[key]); setSelectedRowKeys(newSelectedRowKeys); if (rowType === 'id') { @@ -38,11 +41,7 @@ export function useTableRowSelection(options: Partial = {}) const rowSelection: TableRowSelection> = { selectedRowKeys, onChange: onSelectChange, - selections: [ - Table.SELECTION_ALL, - Table.SELECTION_INVERT, - Table.SELECTION_NONE, - ], + selections: [Table.SELECTION_ALL, Table.SELECTION_INVERT, Table.SELECTION_NONE], }; return { rowSelection, selectedRowIDs, selectedRows, rowReset }; @@ -55,4 +54,4 @@ export const TABLE_PAGINATION = { defaultPageSize: 10, pageSizeOptions: [5, 10, 15, 20], showTotal: (total: number) => Total {total} items, -}; \ No newline at end of file +}; diff --git a/src/icons/SplitIcon.tsx b/src/icons/SplitIcon.tsx index 4192674..e53bf9a 100644 --- a/src/icons/SplitIcon.tsx +++ b/src/icons/SplitIcon.tsx @@ -1,4 +1,4 @@ -import Icon from "@ant-design/icons"; +import Icon from '@ant-design/icons'; import type { CustomIconComponentProps } from '@ant-design/icons/lib/components/Icon'; interface IconProps { @@ -6,12 +6,20 @@ interface IconProps { } export default function SplitIcon(props: Partial) { - return ( - - - - )} - /> + return ( + ( + + + + )} + /> + ); } diff --git a/src/layout/index.scss b/src/layout/index.scss index 07d5cf5..a908db5 100644 --- a/src/layout/index.scss +++ b/src/layout/index.scss @@ -36,4 +36,4 @@ .ant-layout-footer { color: #666 !important; opacity: 0.7; -} \ No newline at end of file +} diff --git a/src/layout/index.tsx b/src/layout/index.tsx index e100e9b..eb027ba 100644 --- a/src/layout/index.tsx +++ b/src/layout/index.tsx @@ -21,21 +21,21 @@ export default function ChatLayout() { setAppInfo({ appName: await getName(), appVersion: await getVersion(), - appTheme: await invoke("get_theme"), + appTheme: await invoke('get_theme'), }); - }) + }); const checkAppUpdate = async () => { await invoke('run_check_update', { silent: false, hasMsg: true }); - } + }; - const isDark = appInfo.appTheme === "dark"; + const isDark = appInfo.appTheme === 'dark'; return ( setCollapsed(value)} @@ -49,41 +49,51 @@ export default function ChatLayout() { zIndex: 999, }} > -
+
+ +
{appInfo.appName} - {appInfo.appVersion} - - - + {appInfo.appVersion} + + + + +
go(i.key)} /> - + ); -}; \ No newline at end of file +} diff --git a/src/main.scss b/src/main.scss index 8bf3c9f..4f530ad 100644 --- a/src/main.scss +++ b/src/main.scss @@ -14,7 +14,8 @@ -webkit-text-size-adjust: 100%; } -html, body { +html, +body { padding: 0; margin: 0; } @@ -100,4 +101,4 @@ html, body { .chatico { cursor: pointer; -} \ No newline at end of file +} diff --git a/src/main.tsx b/src/main.tsx index 488f0cd..3dad92c 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -9,8 +9,8 @@ ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( - + - + , ); diff --git a/src/routes.tsx b/src/routes.tsx index e38d38a..3ce6080 100644 --- a/src/routes.tsx +++ b/src/routes.tsx @@ -23,7 +23,7 @@ import Markdown from '@/view/markdown'; export type ChatRouteMetaObject = { label: string; - icon?: React.ReactNode, + icon?: React.ReactNode; }; type ChatRouteObject = { @@ -32,7 +32,7 @@ type ChatRouteObject = { hideMenu?: boolean; meta?: ChatRouteMetaObject; children?: ChatRouteObject[]; -} +}; export const routes: Array = [ { @@ -116,14 +116,14 @@ export const routes: Array = [ type MenuItem = Required['items'][number]; export const menuItems: MenuItem[] = routes .filter((j) => !j.hideMenu) - .map(i => ({ + .map((i) => ({ ...i.meta, key: i.path || '', children: i?.children ?.filter((j) => !j.hideMenu) - ?.map((j) => ({ ...j.meta, key: `${i.path}/${j.path}` || ''})), + ?.map((j) => ({ ...j.meta, key: `${i.path}/${j.path}` || '' })), })); export default () => { return useRoutes(routes); -}; \ No newline at end of file +}; diff --git a/src/utils.ts b/src/utils.ts index c65f117..e5b2451 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -9,27 +9,28 @@ export const CHAT_DOWNLOAD_JSON = 'chat.download.json'; export const CHAT_AWESOME_JSON = 'chat.awesome.json'; export const CHAT_NOTES_JSON = 'chat.notes.json'; export const CHAT_PROMPTS_CSV = 'chat.prompts.csv'; -export const GITHUB_PROMPTS_CSV_URL = 'https://raw.githubusercontent.com/f/awesome-chatgpt-prompts/main/prompts.csv'; +export const GITHUB_PROMPTS_CSV_URL = + 'https://raw.githubusercontent.com/f/awesome-chatgpt-prompts/main/prompts.csv'; export const DISABLE_AUTO_COMPLETE = { autoCapitalize: 'off', autoComplete: 'off', - spellCheck: false + spellCheck: false, }; export const chatRoot = async () => { - return join(await homeDir(), '.chatgpt') -} + return join(await homeDir(), '.chatgpt'); +}; export const chatModelPath = async (): Promise => { return join(await chatRoot(), CHAT_MODEL_JSON); -} +}; export const chatPromptsPath = async (): Promise => { return join(await chatRoot(), CHAT_PROMPTS_CSV); -} +}; -type readJSONOpts = { defaultVal?: Record, isRoot?: boolean, isList?: boolean }; +type readJSONOpts = { defaultVal?: Record; isRoot?: boolean; isList?: boolean }; export const readJSON = async (path: string, opts: readJSONOpts = {}) => { const { defaultVal = {}, isRoot = false, isList = false } = opts; const root = await chatRoot(); @@ -39,26 +40,39 @@ export const readJSON = async (path: string, opts: readJSONOpts = {}) => { file = await join(root, path); } - if (!await exists(file)) { - if (await dirname(file) !== root) { + if (!(await exists(file))) { + if ((await dirname(file)) !== root) { await createDir(await dirname(file), { recursive: true }); } - await writeTextFile(file, isList ? '[]' : JSON.stringify({ - name: 'ChatGPT', - link: 'https://github.com/lencx/ChatGPT', - ...defaultVal, - }, null, 2)) + await writeTextFile( + file, + isList + ? '[]' + : JSON.stringify( + { + name: 'ChatGPT', + link: 'https://github.com/lencx/ChatGPT', + ...defaultVal, + }, + null, + 2, + ), + ); } try { return JSON.parse(await readTextFile(file)); - } catch(e) { + } catch (e) { return {}; } -} +}; -type writeJSONOpts = { dir?: string, isRoot?: boolean }; -export const writeJSON = async (path: string, data: Record, opts: writeJSONOpts = {}) => { +type writeJSONOpts = { dir?: string; isRoot?: boolean }; +export const writeJSON = async ( + path: string, + data: Record, + opts: writeJSONOpts = {}, +) => { const { isRoot = false } = opts; const root = await chatRoot(); let file = path; @@ -67,13 +81,17 @@ export const writeJSON = async (path: string, data: Record, opts: w file = await join(root, path); } - if (isRoot && !await exists(await dirname(file))) { + if (isRoot && !(await exists(await dirname(file)))) { await createDir(await dirname(file), { recursive: true }); } await writeTextFile(file, JSON.stringify(data, null, 2)); -} +}; export const fmtDate = (date: any) => dayjs(date).format('YYYY-MM-DD HH:mm:ss'); -export const genCmd = (act: string) => act.replace(/\s+|\/+/g, '_').replace(/[^\d\w]/g, '').toLocaleLowerCase(); \ No newline at end of file +export const genCmd = (act: string) => + act + .replace(/\s+|\/+/g, '_') + .replace(/[^\d\w]/g, '') + .toLocaleLowerCase(); diff --git a/src/view/awesome/Form.tsx b/src/view/awesome/Form.tsx index aa55668..89054ce 100644 --- a/src/view/awesome/Form.tsx +++ b/src/view/awesome/Form.tsx @@ -6,7 +6,7 @@ import Tags from '@comps/Tags'; import { DISABLE_AUTO_COMPLETE } from '@/utils'; interface AwesomeFormProps { - record?: Record | null; + record?: Record | null; } const initFormValue = { @@ -28,11 +28,7 @@ const AwesomeForm: ForwardRefRenderFunction = ({ re }, [record]); return ( -
+ = ({ re - ) -} + ); +}; export default forwardRef(AwesomeForm); diff --git a/src/view/awesome/config.tsx b/src/view/awesome/config.tsx index 4e27247..8556a3b 100644 --- a/src/view/awesome/config.tsx +++ b/src/view/awesome/config.tsx @@ -34,7 +34,7 @@ export const awesomeColumns = () => [ dataIndex: 'category', key: 'category', width: 120, - render: (v: string) => {v} + render: (v: string) => {v}, }, { title: 'Tags', @@ -42,7 +42,11 @@ export const awesomeColumns = () => [ key: 'tags', width: 150, render: (v: string[]) => ( - {v?.map(i => {i})} + + {v?.map((i) => ( + {i} + ))} + ), }, { @@ -62,7 +66,7 @@ export const awesomeColumns = () => [ Delete - ) - } - } + ); + }, + }, ]; diff --git a/src/view/awesome/index.tsx b/src/view/awesome/index.tsx index 1660d98..6752625 100644 --- a/src/view/awesome/index.tsx +++ b/src/view/awesome/index.tsx @@ -34,7 +34,8 @@ export default function Awesome() { updateJson(data); opInfo.resetRecord(); } - }, [opInfo.opType, formRef]); + }, [opInfo.opType, + formRef]); const hide = () => { setVisible(false); @@ -46,45 +47,46 @@ export default function Awesome() { const data = opReplace(opInfo?.opRecord?.[opSafeKey], opInfo?.opRecord); updateJson(data); } - }, [opInfo.opTime]) + }, [opInfo.opTime]); - const handleDelete = () => { - - }; + const handleDelete = () => {}; const handleOk = () => { - formRef.current?.form?.validateFields() - .then(async (vals: Record) => { - if (opInfo.opType === 'new') { - const data = opAdd(vals); - await updateJson(data); - opInit(data); - message.success('Data added successfully'); - } - if (opInfo.opType === 'edit') { - const data = opReplace(opInfo?.opRecord?.[opSafeKey], vals); - await updateJson(data); - message.success('Data updated successfully'); - } - hide(); - }) + formRef.current?.form?.validateFields().then(async (vals: Record) => { + if (opInfo.opType === 'new') { + const data = opAdd(vals); + await updateJson(data); + opInit(data); + message.success('Data added successfully'); + } + if (opInfo.opType === 'edit') { + const data = opReplace(opInfo?.opRecord?.[opSafeKey], vals); + await updateJson(data); + message.success('Data updated successfully'); + } + hide(); + }); }; const handleEnable = (isEnable: boolean) => { - const data = opReplaceItems(selectedRowIDs, { enable: isEnable }) + const data = opReplaceItems(selectedRowIDs, { enable: isEnable }); updateJson(data); }; - const modalTitle = `${({ new: 'Create', edit: 'Edit' })[opInfo.opType]} URL`; + const modalTitle = `${{ new: 'Create', edit: 'Edit' }[opInfo.opType]} URL`; return (
- +
{selectedItems.length > 0 && ( <> - +
- ) -} \ No newline at end of file + ); +} diff --git a/src/view/download/config.tsx b/src/view/download/config.tsx index 274c208..4f42bb9 100644 --- a/src/view/download/config.tsx +++ b/src/view/download/config.tsx @@ -10,7 +10,7 @@ import { fmtDate, chatRoot } from '@/utils'; const colorMap: any = { pdf: 'blue', png: 'orange', -} +}; export const downloadColumns = () => [ { @@ -61,20 +61,22 @@ export const downloadColumns = () => [ Delete - ) - } - } + ); + }, + }, ]; const RenderPath = ({ row }: any) => { const [filePath, setFilePath] = useState(''); useInit(async () => { - setFilePath(await getPath(row)); - }) + setFilePath(await getPath(row)); + }); return shell.open(filePath)}>{filePath}; }; export const getPath = async (row: any) => { const isImg = ['png'].includes(row?.ext); - return await path.join(await chatRoot(), 'download', isImg ? 'img' : row.ext, row.id) + `.${row.ext}`; -} + return ( + (await path.join(await chatRoot(), 'download', isImg ? 'img' : row.ext, row.id)) + `.${row.ext}` + ); +}; diff --git a/src/view/download/index.tsx b/src/view/download/index.tsx index 4192651..5684993 100644 --- a/src/view/download/index.tsx +++ b/src/view/download/index.tsx @@ -37,7 +37,12 @@ export default function Download() { (async () => { const record = opInfo?.opRecord; const isImg = ['png'].includes(record?.ext); - const file = await path.join(await chatRoot(), 'download', isImg ? 'img' : record?.ext, `${record?.id}.${record?.ext}`); + const file = await path.join( + await chatRoot(), + 'download', + isImg ? 'img' : record?.ext, + `${record?.id}.${record?.ext}`, + ); if (opInfo.opType === 'preview') { const data = await fs.readBinaryFile(file); const sourceData = renderFile(data, record?.ext); @@ -55,8 +60,8 @@ export default function Download() { message.success('Name has been changed!'); } opInfo.resetRecord(); - })() - }, [opInfo.opType]) + })(); + }, [opInfo.opType]); const handleDelete = async () => { if (opData?.length === selectedRows.length) { @@ -69,10 +74,15 @@ export default function Download() { const rows = selectedRows.map(async (i) => { const isImg = ['png'].includes(i?.ext); - const file = await path.join(await chatRoot(), 'download', isImg ? 'img' : i?.ext, `${i?.id}.${i?.ext}`); + const file = await path.join( + await chatRoot(), + 'download', + isImg ? 'img' : i?.ext, + `${i?.id}.${i?.ext}`, + ); await fs.removeFile(file); return file; - }) + }); Promise.all(rows).then(async () => { await handleRefresh(); message.success('All files selected are cleared!'); @@ -131,5 +141,5 @@ export default function Download() {
- ) -} \ No newline at end of file + ); +} diff --git a/src/view/markdown/index.scss b/src/view/markdown/index.scss index 71f7cc5..964eb74 100644 --- a/src/view/markdown/index.scss +++ b/src/view/markdown/index.scss @@ -1,4 +1,3 @@ - .md-task { margin-bottom: 5px; display: flex; @@ -14,4 +13,4 @@ cursor: pointer; } } -} \ No newline at end of file +} diff --git a/src/view/markdown/index.tsx b/src/view/markdown/index.tsx index 088dd9b..292c7e2 100644 --- a/src/view/markdown/index.tsx +++ b/src/view/markdown/index.tsx @@ -14,7 +14,7 @@ const modeMap: any = { 0: 'split', 1: 'md', 2: 'doc', -} +}; export default function Markdown() { const [filePath, setFilePath] = useState(''); @@ -26,8 +26,8 @@ export default function Markdown() { useInit(async () => { const file = await getPath(state); setFilePath(file); - setSource(await fs.readTextFile(file)) - }) + setSource(await fs.readTextFile(file)); + }); const handleChange = async (v: string) => { await fs.writeTextFile(filePath, v); @@ -46,9 +46,7 @@ export default function Markdown() { history.go(-1)}> - shell.open(filePath)}> - {filePath} - + shell.open(filePath)}>{filePath}
@@ -57,4 +55,4 @@ export default function Markdown() { ); -} \ No newline at end of file +} diff --git a/src/view/model/SyncCustom/Form.tsx b/src/view/model/SyncCustom/Form.tsx index fbf3e3a..9166643 100644 --- a/src/view/model/SyncCustom/Form.tsx +++ b/src/view/model/SyncCustom/Form.tsx @@ -1,4 +1,10 @@ -import { useEffect, useState, ForwardRefRenderFunction, useImperativeHandle, forwardRef } from 'react'; +import { + useEffect, + useState, + ForwardRefRenderFunction, + useImperativeHandle, + forwardRef, +} from 'react'; import { Form, Input, Select, Tooltip } from 'antd'; import { v4 } from 'uuid'; import type { FormProps } from 'antd'; @@ -7,7 +13,7 @@ import { DISABLE_AUTO_COMPLETE, chatRoot } from '@/utils'; import useInit from '@/hooks/useInit'; interface SyncFormProps { - record?: Record | null; + record?: Record | null; type: string; } @@ -54,10 +60,18 @@ const SyncForm: ForwardRefRenderFunction = ({ record, const jsonTip = ( {JSON.stringify([ - { cmd: '', act: '', prompt: '' }, - { cmd: '', act: '', prompt: '' }, - ], null, 2)}} + title={ +
+          {JSON.stringify(
+            [
+              { cmd: '', act: '', prompt: '' },
+              { cmd: '', act: '', prompt: '' },
+            ],
+            null,
+            2,
+          )}
+        
+ } > JSON
@@ -65,10 +79,12 @@ const SyncForm: ForwardRefRenderFunction = ({ record, const csvTip = ( {`"cmd","act","prompt" + title={ +
{`"cmd","act","prompt"
 "cmd","act","prompt"
 "cmd","act","prompt"
-"cmd","act","prompt"`}
} +"cmd","act","prompt"`} + } > CSV
@@ -76,11 +92,7 @@ const SyncForm: ForwardRefRenderFunction = ({ record, return ( <> -
+ = ({ record, label="PATH" name="path" rules={[{ required: true, message: 'Please enter the path!' }]} - > + > = ({ record, {...DISABLE_AUTO_COMPLETE} /> - + + +
-

The file supports only {csvTip} and {jsonTip} formats.

+

+ The file supports only {csvTip} and {jsonTip} formats. +

- ) -} + ); +}; export default forwardRef(SyncForm); diff --git a/src/view/model/SyncCustom/config.tsx b/src/view/model/SyncCustom/config.tsx index d491cfc..bb3721c 100644 --- a/src/view/model/SyncCustom/config.tsx +++ b/src/view/model/SyncCustom/config.tsx @@ -26,7 +26,7 @@ export const syncColumns = () => [ dataIndex: 'path', key: 'path', width: 180, - render: (_: string, row: any) => + render: (_: string, row: any) => , }, { title: 'Last updated', @@ -36,7 +36,7 @@ export const syncColumns = () => [ render: (v: number) => (
- { v ? fmtDate(v) : ''} + {v ? fmtDate(v) : ''}
), }, @@ -56,7 +56,11 @@ export const syncColumns = () => [ > Sync - {row.last_updated && View} + {row.last_updated && ( + + View + + )} actions.setRecord(row, 'edit')}>Edit [ Delete - ) - } - } + ); + }, + }, ]; const RenderPath = ({ row }: any) => { const [filePath, setFilePath] = useState(''); useInit(async () => { - setFilePath(await getPath(row)); - }) - return shell.open(filePath)}>{filePath} + setFilePath(await getPath(row)); + }); + return shell.open(filePath)}>{filePath}; }; export const getPath = async (row: any) => { if (!/^http/.test(row.protocol)) { - return await path.join(await chatRoot(), row.path) + `.${row.ext}`; + return (await path.join(await chatRoot(), row.path)) + `.${row.ext}`; } else { return `${row.protocol}://${row.path}.${row.ext}`; } -} \ No newline at end of file +}; diff --git a/src/view/model/SyncCustom/index.tsx b/src/view/model/SyncCustom/index.tsx index 0ee3767..fec29cb 100644 --- a/src/view/model/SyncCustom/index.tsx +++ b/src/view/model/SyncCustom/index.tsx @@ -10,7 +10,13 @@ import { CHAT_MODEL_JSON, chatRoot, readJSON, genCmd } from '@/utils'; import { syncColumns, getPath } from './config'; import SyncForm from './Form'; -const fmtData = (data: Record[] = []) => (Array.isArray(data) ? data : []).map((i) => ({ ...i, cmd: i.cmd ? i.cmd : genCmd(i.act), tags: ['user-sync'], enable: true })); +const fmtData = (data: Record[] = []) => + (Array.isArray(data) ? data : []).map((i) => ({ + ...i, + cmd: i.cmd ? i.cmd : genCmd(i.act), + tags: ['user-sync'], + enable: true, + })); export default function SyncCustom() { const [isVisible, setVisible] = useState(false); @@ -37,7 +43,10 @@ export default function SyncCustom() { handleSync(filename).then((isOk: boolean) => { opInfo.resetRecord(); if (!isOk) return; - const data = opReplace(opInfo?.opRecord?.[opSafeKey], { ...opInfo?.opRecord, last_updated: Date.now() }); + const data = opReplace(opInfo?.opRecord?.[opSafeKey], { + ...opInfo?.opRecord, + last_updated: Date.now(), + }); modelSet(data); opInfo.resetRecord(); }); @@ -48,9 +57,13 @@ export default function SyncCustom() { if (['delete'].includes(opInfo.opType)) { (async () => { try { - const file = await path.join(await chatRoot(), 'cache_model', `${opInfo?.opRecord?.id}.json`); + const file = await path.join( + await chatRoot(), + 'cache_model', + `${opInfo?.opRecord?.id}.json`, + ); await fs.removeFile(file); - } catch(e) {} + } catch (e) {} const data = opRemove(opInfo?.opRecord?.[opSafeKey]); modelSet(data); opInfo.resetRecord(); @@ -94,29 +107,24 @@ export default function SyncCustom() { }; const handleOk = () => { - formRef.current?.form?.validateFields() - .then((vals: Record) => { - if (opInfo.opType === 'new') { - const data = opAdd(vals); - modelSet(data); - message.success('Data added successfully'); - } - if (opInfo.opType === 'edit') { - const data = opReplace(opInfo?.opRecord?.[opSafeKey], vals); - modelSet(data); - message.success('Data updated successfully'); - } - hide(); - }) + formRef.current?.form?.validateFields().then((vals: Record) => { + if (opInfo.opType === 'new') { + const data = opAdd(vals); + modelSet(data); + message.success('Data added successfully'); + } + if (opInfo.opType === 'edit') { + const data = opReplace(opInfo?.opRecord?.[opSafeKey], vals); + modelSet(data); + message.success('Data updated successfully'); + } + hide(); + }); }; return (
-
- ) -} \ No newline at end of file + ); +} diff --git a/src/view/model/SyncPrompts/config.tsx b/src/view/model/SyncPrompts/config.tsx index b8b4ade..b6381de 100644 --- a/src/view/model/SyncPrompts/config.tsx +++ b/src/view/model/SyncPrompts/config.tsx @@ -41,8 +41,6 @@ export const syncColumns = () => [ dataIndex: 'prompt', key: 'prompt', // width: 300, - render: (v: string) => ( - {v} - ), + render: (v: string) => {v}, }, ]; diff --git a/src/view/model/SyncPrompts/index.scss b/src/view/model/SyncPrompts/index.scss index 4e3ba63..0c7fb60 100644 --- a/src/view/model/SyncPrompts/index.scss +++ b/src/view/model/SyncPrompts/index.scss @@ -1,4 +1,5 @@ -.chat-table-tip, .chat-table-btns { +.chat-table-tip, +.chat-table-btns { display: flex; justify-content: space-between; } diff --git a/src/view/model/SyncPrompts/index.tsx b/src/view/model/SyncPrompts/index.tsx index 8a26a84..e983d37 100644 --- a/src/view/model/SyncPrompts/index.tsx +++ b/src/view/model/SyncPrompts/index.tsx @@ -52,7 +52,7 @@ export default function SyncPrompts() { }, [opInfo.opTime]); const handleEnable = (isEnable: boolean) => { - const data = opReplaceItems(selectedRowIDs, { enable: isEnable }) + const data = opReplaceItems(selectedRowIDs, { enable: isEnable }); modelCacheSet(data); }; @@ -72,7 +72,9 @@ export default function SyncPrompts() {
{selectedItems.length > 0 && ( <> - + Selected {selectedItems.length} items @@ -84,7 +86,11 @@ export default function SyncPrompts() {
- {lastUpdated && Last updated on {fmtDate(lastUpdated)}} + {lastUpdated && ( + + Last updated on {fmtDate(lastUpdated)} + + )}
{record.prompt}
}} + expandable={{ + expandedRowRender: (record) =>
{record.prompt}
, + }} /> - ) -} \ No newline at end of file + ); +} diff --git a/src/view/model/SyncRecord/config.tsx b/src/view/model/SyncRecord/config.tsx index 71321ca..66ffd23 100644 --- a/src/view/model/SyncRecord/config.tsx +++ b/src/view/model/SyncRecord/config.tsx @@ -25,7 +25,11 @@ export const syncColumns = () => [ key: 'tags', // width: 150, render: (v: string[]) => ( - {v?.map(i => {i})} + + {v?.map((i) => ( + {i} + ))} + ), }, { @@ -43,8 +47,6 @@ export const syncColumns = () => [ dataIndex: 'prompt', key: 'prompt', // width: 300, - render: (v: string) => ( - {v} - ), + render: (v: string) => {v}, }, ]; diff --git a/src/view/model/SyncRecord/index.tsx b/src/view/model/SyncRecord/index.tsx index b74344a..d8b60ca 100644 --- a/src/view/model/SyncRecord/index.tsx +++ b/src/view/model/SyncRecord/index.tsx @@ -30,7 +30,7 @@ export default function SyncRecord() { useInit(async () => { setFilePath(await getPath(state)); setJsonPath(await path.join(await chatRoot(), 'cache_model', `${state?.id}.json`)); - }) + }); useEffect(() => { if (modelCacheJson.length <= 0) return; @@ -45,7 +45,7 @@ export default function SyncRecord() { }, [opInfo.opTime]); const handleEnable = (isEnable: boolean) => { - const data = opReplaceItems(selectedRowIDs, { enable: isEnable }) + const data = opReplaceItems(selectedRowIDs, { enable: isEnable }); modelCacheSet(data); }; @@ -58,7 +58,9 @@ export default function SyncRecord() {
{selectedItems.length > 0 && ( <> - + Selected {selectedItems.length} items @@ -70,7 +72,11 @@ export default function SyncRecord() {
- {state?.last_updated && Last updated on {fmtDate(state?.last_updated)}} + {state?.last_updated && ( + + Last updated on {fmtDate(state?.last_updated)} + + )}
{record.prompt}
}} + expandable={{ + expandedRowRender: (record) =>
{record.prompt}
, + }} /> - ) -} \ No newline at end of file + ); +} diff --git a/src/view/model/UserCustom/Form.tsx b/src/view/model/UserCustom/Form.tsx index c810a77..9d4e306 100644 --- a/src/view/model/UserCustom/Form.tsx +++ b/src/view/model/UserCustom/Form.tsx @@ -6,7 +6,7 @@ import Tags from '@comps/Tags'; import { DISABLE_AUTO_COMPLETE } from '@/utils'; interface UserCustomFormProps { - record?: Record | null; + record?: Record | null; } const initFormValue = { @@ -16,7 +16,10 @@ const initFormValue = { prompt: '', }; -const UserCustomForm: ForwardRefRenderFunction = ({ record }, ref) => { +const UserCustomForm: ForwardRefRenderFunction = ( + { record }, + ref, +) => { const [form] = Form.useForm(); useImperativeHandle(ref, () => ({ form })); @@ -27,11 +30,7 @@ const UserCustomForm: ForwardRefRenderFunction = }, [record]); return ( -
+ = - ) -} + ); +}; export default forwardRef(UserCustomForm); diff --git a/src/view/model/UserCustom/config.tsx b/src/view/model/UserCustom/config.tsx index 0450043..162dc66 100644 --- a/src/view/model/UserCustom/config.tsx +++ b/src/view/model/UserCustom/config.tsx @@ -7,7 +7,7 @@ export const modelColumns = () => [ fixed: 'left', width: 120, key: 'cmd', - render: (v: string) => /{v} + render: (v: string) => /{v}, }, { title: 'Act', @@ -21,7 +21,11 @@ export const modelColumns = () => [ key: 'tags', width: 150, render: (v: string[]) => ( - {v?.map(i => {i})} + + {v?.map((i) => ( + {i} + ))} + ), }, { @@ -39,9 +43,7 @@ export const modelColumns = () => [ dataIndex: 'prompt', key: 'prompt', width: 300, - render: (v: string) => ( - {v} - ), + render: (v: string) => {v}, }, { title: 'Action', @@ -61,5 +63,5 @@ export const modelColumns = () => [ ), - } + }, ]; diff --git a/src/view/model/UserCustom/index.tsx b/src/view/model/UserCustom/index.tsx index a98615a..591d35f 100644 --- a/src/view/model/UserCustom/index.tsx +++ b/src/view/model/UserCustom/index.tsx @@ -50,10 +50,10 @@ export default function LanguageModel() { const data = opReplace(opInfo?.opRecord?.[opSafeKey], opInfo?.opRecord); modelCacheSet(data); } - }, [opInfo.opTime]) + }, [opInfo.opTime]); const handleEnable = (isEnable: boolean) => { - const data = opReplaceItems(selectedRowIDs, { enable: isEnable }) + const data = opReplaceItems(selectedRowIDs, { enable: isEnable }); modelCacheSet(data); }; @@ -63,38 +63,51 @@ export default function LanguageModel() { }; const handleOk = () => { - formRef.current?.form?.validateFields() - .then(async (vals: Record) => { - if (modelCacheJson.map((i: any) => i.cmd).includes(vals.cmd) && opInfo?.opRecord?.cmd !== vals.cmd) { - message.warning(`"cmd: /${vals.cmd}" already exists, please change the "${vals.cmd}" name and resubmit.`); - return; - } - let data = []; - switch (opInfo.opType) { - case 'new': data = opAdd(vals); break; - case 'edit': data = opReplace(opInfo?.opRecord?.[opSafeKey], vals); break; - default: break; - } - await modelCacheSet(data); - opInit(data); - modelSet({ - id: 'user_custom', - last_updated: Date.now(), - }); - hide(); - }) + formRef.current?.form?.validateFields().then(async (vals: Record) => { + if ( + modelCacheJson.map((i: any) => i.cmd).includes(vals.cmd) && + opInfo?.opRecord?.cmd !== vals.cmd + ) { + message.warning( + `"cmd: /${vals.cmd}" already exists, please change the "${vals.cmd}" name and resubmit.`, + ); + return; + } + let data = []; + switch (opInfo.opType) { + case 'new': + data = opAdd(vals); + break; + case 'edit': + data = opReplace(opInfo?.opRecord?.[opSafeKey], vals); + break; + default: + break; + } + await modelCacheSet(data); + opInit(data); + modelSet({ + id: 'user_custom', + last_updated: Date.now(), + }); + hide(); + }); }; - const modalTitle = `${({ new: 'Create', edit: 'Edit' })[opInfo.opType]} Model`; + const modalTitle = `${{ new: 'Create', edit: 'Edit' }[opInfo.opType]} Model`; return (
- +
{selectedItems.length > 0 && ( <> - + Selected {selectedItems.length} items @@ -103,7 +116,11 @@ export default function LanguageModel() {
- {lastUpdated && Last updated on {fmtDate(lastUpdated)}} + {lastUpdated && ( + + Last updated on {fmtDate(lastUpdated)} + + )}
{record.prompt}
}} + expandable={{ + expandedRowRender: (record) =>
{record.prompt}
, + }} /> - ) -} \ No newline at end of file + ); +} diff --git a/src/view/notes/config.tsx b/src/view/notes/config.tsx index 224aea9..53ed522 100644 --- a/src/view/notes/config.tsx +++ b/src/view/notes/config.tsx @@ -41,7 +41,9 @@ export const notesColumns = () => [ return ( actions.setRecord(row, 'preview')}>Preview - Edit + + Edit + actions.setRecord(row, 'delete')} @@ -51,20 +53,20 @@ export const notesColumns = () => [ Delete - ) - } - } + ); + }, + }, ]; const RenderPath = ({ row }: any) => { const [filePath, setFilePath] = useState(''); useInit(async () => { - setFilePath(await getPath(row)); - }) + setFilePath(await getPath(row)); + }); return shell.open(filePath)}>{filePath}; }; export const getPath = async (row: any) => { const isImg = ['png'].includes(row?.ext); - return await path.join(await chatRoot(), 'notes', row.id) + `.${row.ext}`; -} + return (await path.join(await chatRoot(), 'notes', row.id)) + `.${row.ext}`; +}; diff --git a/src/view/notes/index.tsx b/src/view/notes/index.tsx index c007ea1..9bbc626 100644 --- a/src/view/notes/index.tsx +++ b/src/view/notes/index.tsx @@ -46,8 +46,8 @@ export default function Notes() { message.success('Name has been changed!'); } opInfo.resetRecord(); - })() - }, [opInfo.opType]) + })(); + }, [opInfo.opType]); const handleDelete = async () => { if (opData?.length === selectedRows.length) { @@ -62,7 +62,7 @@ export default function Notes() { const file = await path.join(await chatRoot(), 'notes', `${i?.id}.${i?.ext}`); await fs.removeFile(file); return file; - }) + }); Promise.all(rows).then(async () => { await handleRefresh(); message.success('All files selected are cleared!'); @@ -122,5 +122,5 @@ export default function Notes() { - ) -} \ No newline at end of file + ); +} diff --git a/src/view/settings/General.tsx b/src/view/settings/General.tsx index 9b2189d..88207b1 100644 --- a/src/view/settings/General.tsx +++ b/src/view/settings/General.tsx @@ -9,37 +9,47 @@ import { DISABLE_AUTO_COMPLETE } from '@/utils'; const AutoUpdateLabel = () => { return ( - Auto Update - {' '} - -
Auto Update Policy
-
Prompt: prompt to install
-
Silent: install silently
- {/*
Disable: disable auto update
*/} - - )}>
+ Auto Update{' '} + +
Auto Update Policy
+
+ Prompt: prompt to install +
+
+ Silent: install silently +
+ {/*
Disable: disable auto update
*/} + + } + > + +
- ) -} + ); +}; const GlobalShortcutLabel = () => { return (
- Global Shortcut - {' '} - -
Shortcut definition, modifiers and key separated by "+" e.g. CmdOrControl+Q
-
If empty, the shortcut is disabled.
- https://tauri.app/v1/api/js/globalshortcut -
- )}> + Global Shortcut{' '} + +
Shortcut definition, modifiers and key separated by "+" e.g. CmdOrControl+Q
+
If empty, the shortcut is disabled.
+ + https://tauri.app/v1/api/js/globalshortcut + + + } + >
- ) -} + ); +}; export default function General() { const [platformInfo, setPlatform] = useState(''); @@ -62,9 +72,7 @@ export default function General() { Light Dark - {["darwin", "windows"].includes(platformInfo) && ( - System - )} + {['darwin', 'windows'].includes(platformInfo) && System} } name="auto_update"> @@ -78,5 +86,5 @@ export default function General() { - ) + ); } diff --git a/src/view/settings/MainWindow.tsx b/src/view/settings/MainWindow.tsx index d5e511e..99a7378 100644 --- a/src/view/settings/MainWindow.tsx +++ b/src/view/settings/MainWindow.tsx @@ -9,25 +9,39 @@ import { DISABLE_AUTO_COMPLETE } from '@/utils'; const OriginLabel = ({ url }: { url: string }) => { return ( - Switch Origin + Switch Origin{' '} + + + - ) -} + ); +}; const PopupSearchLabel = () => { return ( - Pop-up Search - {' '} - -
Generate images according to the content: Select the ChatGPT content with the mouse, no more than 400 characters. the DALL·E 2 button appears, and click to jump (Note: because the search content filled by the script cannot trigger the event directly, you need to enter a space in the input box to make the button clickable).
-
The application is built using Tauri, and due to its security restrictions, some of the action buttons will not work, so we recommend going to your browser.
- - )}>
+ Pop-up Search{' '} + +
+ Generate images according to the content: Select the ChatGPT content with the mouse, + no more than 400 characters. the DALL·E 2 button appears, and click to jump + (Note: because the search content filled by the script cannot trigger the event + directly, you need to enter a space in the input box to make the button clickable). +
+
+ The application is built using Tauri, and due to its security restrictions, some of + the action buttons will not work, so we recommend going to your browser. +
+ + } + > + +
- ) -} + ); +}; export default function General() { const [chatConf, setChatConf] = useState(null); @@ -46,8 +60,12 @@ export default function General() { - + - ) + ); } diff --git a/src/view/settings/TrayWindow.tsx b/src/view/settings/TrayWindow.tsx index 7ee94f1..8fcab50 100644 --- a/src/view/settings/TrayWindow.tsx +++ b/src/view/settings/TrayWindow.tsx @@ -9,8 +9,12 @@ export default function General() { - + - ) + ); } diff --git a/src/view/settings/index.tsx b/src/view/settings/index.tsx index a6a442a..96f87f8 100644 --- a/src/view/settings/index.tsx +++ b/src/view/settings/index.tsx @@ -21,7 +21,7 @@ export default function Settings() { useEffect(() => { form.setFieldsValue(clone(chatConf)); - }, [chatConf]) + }, [chatConf]); const onCancel = () => { form.setFieldsValue(chatConf); @@ -31,7 +31,7 @@ export default function Settings() { const chatData = await invoke('reset_chat_conf'); setChatConf(chatData); const isOk = await dialog.ask(`Configuration reset successfully, whether to restart?`, { - title: 'ChatGPT Preferences' + title: 'ChatGPT Preferences', }); if (isOk) { process.relaunch(); @@ -44,7 +44,7 @@ export default function Settings() { if (!isEqual(omit(chatConf, ['default_origin']), values)) { await invoke('form_confirm', { data: values, label: 'main' }); const isOk = await dialog.ask(`Configuration saved successfully, whether to restart?`, { - title: 'ChatGPT Preferences' + title: 'ChatGPT Preferences', }); if (isOk) { process.relaunch(); @@ -75,11 +75,15 @@ export default function Settings() { - - + + - ) -} \ No newline at end of file + ); +} diff --git a/tsconfig.json b/tsconfig.json index 474aa73..9505215 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -20,7 +20,7 @@ "@/*": ["src/*"], "@view/*": ["src/view/*"], "@comps/*": ["src/components/*"], - "@layout/*": ["src/layout/*"], + "@layout/*": ["src/layout/*"] } }, "include": ["src"], diff --git a/vite.config.ts b/vite.config.ts index debf8b2..c69da5d 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,6 +1,6 @@ -import { defineConfig } from "vite"; -import react from "@vitejs/plugin-react"; -import tsconfigPaths from "vite-tsconfig-paths"; +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import tsconfigPaths from 'vite-tsconfig-paths'; // https://vitejs.dev/config/ export default defineConfig({ @@ -16,12 +16,12 @@ export default defineConfig({ }, // to make use of `TAURI_DEBUG` and other env variables // https://tauri.studio/v1/api/config#buildconfig.beforedevcommand - envPrefix: ["VITE_", "TAURI_"], + envPrefix: ['VITE_', 'TAURI_'], build: { // Tauri supports es2021 - target: ["es2021", "chrome100", "safari13"], + target: ['es2021', 'chrome100', 'safari13'], // don't minify for debug builds - minify: !process.env.TAURI_DEBUG ? "esbuild" : false, + minify: !process.env.TAURI_DEBUG ? 'esbuild' : false, // produce sourcemaps for debug builds sourcemap: !!process.env.TAURI_DEBUG, }, From d84c11319dfbc024bbd58ed91e042c21b413059d Mon Sep 17 00:00:00 2001 From: lencx Date: Sun, 22 Jan 2023 18:23:28 +0800 Subject: [PATCH 14/30] chore: pretty --- src/view/awesome/index.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/view/awesome/index.tsx b/src/view/awesome/index.tsx index 6752625..0b75c82 100644 --- a/src/view/awesome/index.tsx +++ b/src/view/awesome/index.tsx @@ -35,6 +35,7 @@ export default function Awesome() { opInfo.resetRecord(); } }, [opInfo.opType, + formRef]); const hide = () => { From 6a7eabf5cbda24030ea47556824de59ad90309bf Mon Sep 17 00:00:00 2001 From: lencx Date: Sun, 22 Jan 2023 20:16:14 +0800 Subject: [PATCH 15/30] chore: husky --- .husky/pre-commit | 6 ++++++ package.json | 10 +++------- 2 files changed, 9 insertions(+), 7 deletions(-) create mode 100755 .husky/pre-commit diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 0000000..aef59bf --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,6 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" + +npm run pretty-quick +cargo fmt +git add . \ No newline at end of file diff --git a/package.json b/package.json index 8c52719..96e77cd 100644 --- a/package.json +++ b/package.json @@ -15,8 +15,9 @@ "fmt:rs": "cargo fmt", "tr": "tr", "tauri": "tauri", - "prettier": "prettier -c --write '**/*'", - "pretty-quick": "pretty-quick" + "prettier": "prettier -c --write '**/*.{js,md,ts,tsx,yml}'", + "pretty-quick": "pretty-quick --staged", + "prepare": "husky install" }, "license": "MIT", "author": "lencx ", @@ -35,11 +36,6 @@ "type": "git", "url": "https://github.com/lencx/ChatGPT" }, - "husky": { - "hooks": { - "pre-commit": "pretty-quick --staged" - } - }, "dependencies": { "@ant-design/icons": "^4.8.0", "@monaco-editor/react": "^4.4.6", From f1ec7c64959a3cab7a9205e0da801bb5d012ab7f Mon Sep 17 00:00:00 2001 From: lencx Date: Sun, 22 Jan 2023 20:17:51 +0800 Subject: [PATCH 16/30] chore: remove lint --- .github/workflows/lint.yml | 31 ------------------------------- 1 file changed, 31 deletions(-) delete mode 100644 .github/workflows/lint.yml diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml deleted file mode 100644 index 28d01d5..0000000 --- a/.github/workflows/lint.yml +++ /dev/null @@ -1,31 +0,0 @@ -name: Lint CI -on: [push, pull_request] - -jobs: - lint: - runs-on: ubuntu-latest - steps: - - name: Setup | Checkout - uses: actions/checkout@v3 - - - name: Setup | Ubuntu dependencies - run: sudo apt install libasound2-dev libudev-dev pkg-config - - - name: Setup | Toolchain (clippy, rustfmt) - uses: dtolnay/rust-toolchain@stable - with: - components: clippy, rustfmt - - - uses: Swatinem/rust-cache@v2 - - - name: Lint | Clippy - - uses: actions-rs/cargo@v1 - with: - command: clippy - args: --all-targets --all-features -- -D warnings - - - name: Lint | Rustfmt - - uses: actions-rs/cargo@v1 - with: - command: fmt - args: --all -- --check From 84a29d7cda8a86e27e441aa358833a46b567d721 Mon Sep 17 00:00:00 2001 From: lencx Date: Mon, 23 Jan 2023 00:36:39 +0800 Subject: [PATCH 17/30] chore: window --- src-tauri/src/app/menu.rs | 8 ++---- src-tauri/src/app/setup.rs | 26 ++++++++++-------- src-tauri/src/app/window.rs | 38 +++++++++++++++++---------- src-tauri/src/conf.rs | 13 +++++---- src-tauri/src/utils.rs | 7 ++--- src/components/SwitchOrigin/index.tsx | 30 +++++++++++++++++++++ src/view/awesome/config.tsx | 2 ++ src/view/settings/General.tsx | 5 ++++ src/view/settings/MainWindow.tsx | 26 ++---------------- src/view/settings/TrayWindow.tsx | 2 ++ src/view/settings/index.tsx | 4 +-- 11 files changed, 96 insertions(+), 65 deletions(-) create mode 100644 src/components/SwitchOrigin/index.tsx diff --git a/src-tauri/src/app/menu.rs b/src-tauri/src/app/menu.rs index 4860e6c..daaddcb 100644 --- a/src-tauri/src/app/menu.rs +++ b/src-tauri/src/app/menu.rs @@ -137,16 +137,12 @@ pub fn init() -> Menu { CustomMenuItem::new("go_conf".to_string(), "Go to Config") .accelerator("CmdOrCtrl+Shift+G") .into(), - CustomMenuItem::new("clear_conf".to_string(), "Clear Config") - .accelerator("CmdOrCtrl+Shift+D") - .into(), CustomMenuItem::new("restart".to_string(), "Restart ChatGPT") .accelerator("CmdOrCtrl+Shift+R") .into(), + CustomMenuItem::new("clear_conf".to_string(), "Clear Config").into(), MenuItem::Separator.into(), - CustomMenuItem::new("awesome".to_string(), "Awesome ChatGPT") - .accelerator("CmdOrCtrl+Shift+A") - .into(), + CustomMenuItem::new("awesome".to_string(), "Awesome ChatGPT").into(), CustomMenuItem::new("buy_coffee".to_string(), "Buy lencx a coffee").into(), ]), ); diff --git a/src-tauri/src/app/setup.rs b/src-tauri/src/app/setup.rs index ab2c133..a2a9bc0 100644 --- a/src-tauri/src/app/setup.rs +++ b/src-tauri/src/app/setup.rs @@ -50,7 +50,7 @@ pub fn init(app: &mut App) -> std::result::Result<(), Box } else { let app = app.handle(); tauri::async_runtime::spawn(async move { - let mut main_win = WindowBuilder::new(&app, "core", WindowUrl::App(url.into())) + let mut main_win = WindowBuilder::new(&app, "core", WindowUrl::App(url.clone().into())) .title("ChatGPT") .resizable(true) .fullscreen(false) @@ -60,22 +60,26 @@ pub fn init(app: &mut App) -> std::result::Result<(), Box main_win = main_win.hidden_title(true); } + if url == "https://chat.openai.com" { + main_win = main_win + .initialization_script(include_str!("../vendors/floating-ui-core.js")) + .initialization_script(include_str!("../vendors/floating-ui-dom.js")) + .initialization_script(include_str!("../vendors/html2canvas.js")) + .initialization_script(include_str!("../vendors/jspdf.js")) + .initialization_script(include_str!("../vendors/turndown.js")) + .initialization_script(include_str!("../vendors/turndown-plugin-gfm.js")) + .initialization_script(include_str!("../scripts/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")) + } + main_win .theme(theme) .always_on_top(chat_conf.stay_on_top) .title_bar_style(ChatConfJson::titlebar()) .initialization_script(&utils::user_script()) - .initialization_script(include_str!("../vendors/floating-ui-core.js")) - .initialization_script(include_str!("../vendors/floating-ui-dom.js")) - .initialization_script(include_str!("../vendors/html2canvas.js")) - .initialization_script(include_str!("../vendors/jspdf.js")) - .initialization_script(include_str!("../vendors/turndown.js")) - .initialization_script(include_str!("../vendors/turndown-plugin-gfm.js")) .initialization_script(include_str!("../scripts/core.js")) - .initialization_script(include_str!("../scripts/popup.core.js")) - .initialization_script(include_str!("../scripts/export.js")) - .initialization_script(include_str!("../scripts/markdown.export.js")) - .initialization_script(include_str!("../scripts/cmd.js")) .user_agent(&chat_conf.ua_window) .build() .unwrap(); diff --git a/src-tauri/src/app/window.rs b/src-tauri/src/app/window.rs index 990e7b3..7f4164e 100644 --- a/src-tauri/src/app/window.rs +++ b/src-tauri/src/app/window.rs @@ -9,20 +9,30 @@ pub fn tray_window(handle: &tauri::AppHandle) { let app = handle.clone(); tauri::async_runtime::spawn(async move { - WindowBuilder::new(&app, "tray", WindowUrl::App(chat_conf.origin.into())) - .title("ChatGPT") - .resizable(false) - .fullscreen(false) - .inner_size(360.0, 540.0) - .decorations(false) - .always_on_top(true) - .theme(theme) - .initialization_script(&utils::user_script()) - .initialization_script(include_str!("../vendors/floating-ui-core.js")) - .initialization_script(include_str!("../vendors/floating-ui-dom.js")) - .initialization_script(include_str!("../scripts/core.js")) - .initialization_script(include_str!("../scripts/cmd.js")) - .initialization_script(include_str!("../scripts/popup.core.js")) + let mut tray_win = WindowBuilder::new( + &app, + "tray", + WindowUrl::App(chat_conf.tray_origin.clone().into()), + ) + .title("ChatGPT") + .resizable(false) + .fullscreen(false) + .inner_size(360.0, 540.0) + .decorations(false) + .always_on_top(true) + .theme(theme) + .initialization_script(&utils::user_script()) + .initialization_script(include_str!("../scripts/core.js")); + + if chat_conf.tray_origin == "https://chat.openai.com" { + 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/popup.core.js")) + } + + tray_win .user_agent(&chat_conf.ua_tray) .build() .unwrap() diff --git a/src-tauri/src/conf.rs b/src-tauri/src/conf.rs index 9f6c648..56b9ab0 100644 --- a/src-tauri/src/conf.rs +++ b/src-tauri/src/conf.rs @@ -26,10 +26,11 @@ pub const DEFAULT_CHAT_CONF: &str = r#"{ "popup_search": false, "global_shortcut": "", "hide_dock_icon": false, - "default_origin": "https://chat.openai.com", "origin": "https://chat.openai.com", + "tray_origin": "https://chat.openai.com", + "default_origin": "https://chat.openai.com", "ua_window": "", - "ua_tray": "" + "ua_tray": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1" }"#; pub const DEFAULT_CHAT_CONF_MAC: &str = r#"{ "stay_on_top": false, @@ -40,10 +41,11 @@ pub const DEFAULT_CHAT_CONF_MAC: &str = r#"{ "popup_search": false, "global_shortcut": "", "hide_dock_icon": false, - "default_origin": "https://chat.openai.com", "origin": "https://chat.openai.com", + "tray_origin": "https://chat.openai.com", + "default_origin": "https://chat.openai.com", "ua_window": "", - "ua_tray": "" + "ua_tray": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1" }"#; #[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] @@ -59,8 +61,9 @@ pub struct ChatConfJson { pub tray: bool, pub popup_search: bool, pub stay_on_top: bool, - pub default_origin: String, pub origin: String, + pub tray_origin: String, + pub default_origin: String, pub ua_window: String, pub ua_tray: String, pub global_shortcut: Option, diff --git a/src-tauri/src/utils.rs b/src-tauri/src/utils.rs index d0735c9..9945bcd 100644 --- a/src-tauri/src/utils.rs +++ b/src-tauri/src/utils.rs @@ -84,9 +84,10 @@ pub fn open_file(path: PathBuf) { pub fn clear_conf(app: &tauri::AppHandle) { let root = chat_root(); - let app2 = app.clone(); let msg = format!( - "Path: {}\nAre you sure to clear all ChatGPT configurations? Please backup in advance if necessary!", + "Path: {}\n + Are you sure you want to clear all ChatGPT configurations? Performing this operation data can not be restored, please back up in advance.\n + Note: The application will exit automatically after the configuration cleanup!", root.to_string_lossy() ); tauri::api::dialog::ask( @@ -96,7 +97,7 @@ pub fn clear_conf(app: &tauri::AppHandle) { move |is_ok| { if is_ok { fs::remove_dir_all(root).unwrap(); - tauri::api::process::restart(&app2.env()); + std::process::exit(0); } }, ); diff --git a/src/components/SwitchOrigin/index.tsx b/src/components/SwitchOrigin/index.tsx new file mode 100644 index 0000000..70cf883 --- /dev/null +++ b/src/components/SwitchOrigin/index.tsx @@ -0,0 +1,30 @@ +import { FC } from 'react'; +import { Form, Select, Tag } from 'antd'; + +import useJson from '@/hooks/useJson'; +import { DISABLE_AUTO_COMPLETE, CHAT_CONF_JSON, CHAT_AWESOME_JSON } from '@/utils'; + +interface SwitchOriginProps { + name: string; +} + +const SwitchOrigin: FC = ({ name }) => { + const { json: list = [] } = useJson(CHAT_AWESOME_JSON); + + return ( + Switch Origin ({name === 'origin' ? 'Main' : 'SystemTray'})} + name={name} + > + + + ); +}; + +export default SwitchOrigin; diff --git a/src/view/awesome/config.tsx b/src/view/awesome/config.tsx index 8556a3b..164c227 100644 --- a/src/view/awesome/config.tsx +++ b/src/view/awesome/config.tsx @@ -1,4 +1,5 @@ import { Tag, Space, Popconfirm, Switch } from 'antd'; +import { open } from '@tauri-apps/api/shell'; export const awesomeColumns = () => [ { @@ -13,6 +14,7 @@ export const awesomeColumns = () => [ dataIndex: 'url', key: 'url', width: 200, + render: (v: string) => open(v)}>{v}, }, // { // title: 'Icon', diff --git a/src/view/settings/General.tsx b/src/view/settings/General.tsx index 88207b1..6daddb8 100644 --- a/src/view/settings/General.tsx +++ b/src/view/settings/General.tsx @@ -68,6 +68,11 @@ export default function General() { )} + {platformInfo === 'darwin' && ( + + + + )} Light diff --git a/src/view/settings/MainWindow.tsx b/src/view/settings/MainWindow.tsx index 99a7378..18f7a6d 100644 --- a/src/view/settings/MainWindow.tsx +++ b/src/view/settings/MainWindow.tsx @@ -1,22 +1,9 @@ -import { useState } from 'react'; import { Form, Switch, Input, Tooltip } from 'antd'; import { QuestionCircleOutlined } from '@ant-design/icons'; -import { invoke } from '@tauri-apps/api'; -import useInit from '@/hooks/useInit'; +import SwitchOrigin from '@/components/SwitchOrigin'; import { DISABLE_AUTO_COMPLETE } from '@/utils'; -const OriginLabel = ({ url }: { url: string }) => { - return ( - - Switch Origin{' '} - - - - - ); -}; - const PopupSearchLabel = () => { return ( @@ -44,21 +31,12 @@ const PopupSearchLabel = () => { }; export default function General() { - const [chatConf, setChatConf] = useState(null); - - useInit(async () => { - const chatData = await invoke('get_chat_conf'); - setChatConf(chatData); - }); - return ( <> } name="popup_search" valuePropName="checked"> - } name="origin"> - - + + Date: Mon, 23 Jan 2023 10:56:07 +0800 Subject: [PATCH 18/30] chore: settings --- src/components/SwitchOrigin/index.tsx | 27 ++++++++++++++++++------ src/layout/index.scss | 4 ++++ src/layout/index.tsx | 9 ++++++-- src/main.scss | 1 - src/view/model/SyncCustom/index.tsx | 7 ++++++- src/view/settings/TrayWindow.tsx | 18 ++++++++++++++-- src/view/settings/index.tsx | 30 ++++++++++++++++++++------- 7 files changed, 76 insertions(+), 20 deletions(-) diff --git a/src/components/SwitchOrigin/index.tsx b/src/components/SwitchOrigin/index.tsx index 70cf883..16e6dee 100644 --- a/src/components/SwitchOrigin/index.tsx +++ b/src/components/SwitchOrigin/index.tsx @@ -1,9 +1,10 @@ import { FC } from 'react'; -import { Form, Select, Tag } from 'antd'; +import { Link } from 'react-router-dom'; +import { Form, Select, Tag, Tooltip } from 'antd'; +import { QuestionCircleOutlined } from '@ant-design/icons'; import useJson from '@/hooks/useJson'; -import { DISABLE_AUTO_COMPLETE, CHAT_CONF_JSON, CHAT_AWESOME_JSON } from '@/utils'; - +import { DISABLE_AUTO_COMPLETE, CHAT_AWESOME_JSON } from '@/utils'; interface SwitchOriginProps { name: string; } @@ -13,13 +14,27 @@ const SwitchOrigin: FC = ({ name }) => { return ( Switch Origin ({name === 'origin' ? 'Main' : 'SystemTray'})} + label={ + + Switch Origin ({name === 'origin' ? 'Main' : 'SystemTray'}){' '} + + If you need to set a new URL as the application loading window, please add the URL + in the Awesome menu and then select it. + + } + > + + + + } name={name} > diff --git a/src/layout/index.scss b/src/layout/index.scss index a908db5..df8c069 100644 --- a/src/layout/index.scss +++ b/src/layout/index.scss @@ -23,6 +23,10 @@ -webkit-user-select: none; } +.ant-layout-sider-children { + overflow-y: auto; +} + .chat-container { padding: 20px; overflow: hidden; diff --git a/src/layout/index.tsx b/src/layout/index.tsx index eb027ba..d6fcd97 100644 --- a/src/layout/index.tsx +++ b/src/layout/index.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import { Layout, Menu, Tooltip, ConfigProvider, theme, Tag } from 'antd'; import { SyncOutlined } from '@ant-design/icons'; import { useNavigate, useLocation } from 'react-router-dom'; @@ -15,8 +15,13 @@ export default function ChatLayout() { const [collapsed, setCollapsed] = useState(false); const [appInfo, setAppInfo] = useState>({}); const location = useLocation(); + const [menuKey, setMenuKey] = useState(location.pathname); const go = useNavigate(); + useEffect(() => { + setMenuKey(location.pathname); + }, [location.pathname]); + useInit(async () => { setAppInfo({ appName: await getName(), @@ -65,7 +70,7 @@ export default function ChatLayout() { -
{ + return ( + + User Agent (SystemTray){' '} + For a better experience, we recommend using the Mobile User-Agent.} + > + + + + ); +}; + export default function General() { return ( <> @@ -10,7 +24,7 @@ export default function General() { - + } name="ua_tray"> (null); + const [filePath, setPath] = useState(''); useInit(async () => { - const chatData = await invoke('get_chat_conf'); - setChatConf(chatData); + setChatConf(await invoke('get_chat_conf')); + setPath(await path.join(await chatRoot(), CHAT_CONF_JSON)); }); useEffect(() => { @@ -78,9 +79,22 @@ export default function Settings() { - + + Are you sure you want to reset the configuration file + shell.open(filePath)} style={{ margin: '0 5px' }}> + {filePath} + + to the default? + + } + onConfirm={onReset} + okText="Yes" + cancelText="No" + > + + From 1f573102d3eddc66ed7af3594e5f6138db0bcec1 Mon Sep 17 00:00:00 2001 From: lencx Date: Mon, 23 Jan 2023 20:29:05 +0800 Subject: [PATCH 19/30] chore: about --- package.json | 2 + src-tauri/src/app/cmd.rs | 14 +++++ src-tauri/src/main.rs | 1 + src/components/Markdown/index.scss | 6 ++ src/components/Markdown/index.tsx | 2 + src/routes.tsx | 12 +++- src/utils.ts | 1 + src/view/about/index.scss | 18 ++++++ src/view/about/index.tsx | 88 ++++++++++++++++++++++++++++++ 9 files changed, 143 insertions(+), 1 deletion(-) create mode 100644 src/view/about/index.scss create mode 100644 src/view/about/index.tsx diff --git a/package.json b/package.json index 96e77cd..387d4e8 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,8 @@ "react-resizable-panels": "^0.0.33", "react-router-dom": "^6.4.5", "react-syntax-highlighter": "^15.5.0", + "rehype-raw": "^6.1.1", + "remark-comment-config": "^7.0.1", "remark-gfm": "^3.0.1", "uuid": "^9.0.0" }, diff --git a/src-tauri/src/app/cmd.rs b/src-tauri/src/app/cmd.rs index 2127af8..0867256 100644 --- a/src-tauri/src/app/cmd.rs +++ b/src-tauri/src/app/cmd.rs @@ -354,6 +354,20 @@ pub async fn sync_prompts(app: AppHandle, time: u64) -> Option> None } +#[command] +pub async fn get_data(app: AppHandle, url: String, is_msg: Option) -> Option { + let is_msg = is_msg.unwrap_or(false); + let res = if is_msg { + utils::get_data(&url, Some(&app)).await + } else { + utils::get_data(&url, None).await + }; + res.unwrap_or_else(|err| { + info!("chatgpt_client_http_error: {}", err); + None + }) +} + #[command] pub async fn sync_user_prompts(url: String, data_type: String) -> Option> { let res = utils::get_data(&url, None).await.unwrap_or_else(|err| { diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 0468ac8..2be1464 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -78,6 +78,7 @@ async fn main() { cmd::cmd_list, cmd::download_list, cmd::get_download_list, + cmd::get_data, fs_extra::metadata, ]) .setup(setup::init) diff --git a/src/components/Markdown/index.scss b/src/components/Markdown/index.scss index 5c0bc57..64745c0 100644 --- a/src/components/Markdown/index.scss +++ b/src/components/Markdown/index.scss @@ -13,6 +13,12 @@ code { font-family: monospace, monospace; } + + code { + background-color: rgba(200, 200, 200, 0.4); + padding: 2px 4px; + border-radius: 5px; + } } .md-main { diff --git a/src/components/Markdown/index.tsx b/src/components/Markdown/index.tsx index 7277ebf..c77bc0b 100644 --- a/src/components/Markdown/index.tsx +++ b/src/components/Markdown/index.tsx @@ -2,6 +2,7 @@ import { FC } from 'react'; import clsx from 'clsx'; import ReactMarkdown from 'react-markdown'; import remarkGfm from 'remark-gfm'; +import rehypeRaw from 'rehype-raw'; import SyntaxHighlighter from 'react-syntax-highlighter'; import agate from 'react-syntax-highlighter/dist/esm/styles/hljs/agate'; @@ -20,6 +21,7 @@ const Markdown: FC = ({ children, className }) => { children={children} linkTarget="_blank" remarkPlugins={[remarkGfm]} + rehypePlugins={[rehypeRaw]} components={{ code({ node, inline, className, children, ...props }) { const match = /language-(\w+)/.exec(className || ''); diff --git a/src/routes.tsx b/src/routes.tsx index 3ce6080..f3e711f 100644 --- a/src/routes.tsx +++ b/src/routes.tsx @@ -8,10 +8,12 @@ import { DownloadOutlined, FormOutlined, GlobalOutlined, + InfoCircleOutlined, } from '@ant-design/icons'; import type { MenuProps } from 'antd'; import Settings from '@/view/settings'; +import About from '@/view/about'; import Awesome from '@/view/awesome'; import UserCustom from '@/view/model/UserCustom'; import SyncPrompts from '@/view/model/SyncPrompts'; @@ -96,7 +98,7 @@ export const routes: Array = [ ], }, { - path: 'download', + path: '/download', element: , meta: { label: 'Download', @@ -111,6 +113,14 @@ export const routes: Array = [ icon: , }, }, + { + path: '/about', + element: , + meta: { + label: 'About', + icon: , + }, + }, ]; type MenuItem = Required['items'][number]; diff --git a/src/utils.ts b/src/utils.ts index e5b2451..7582c68 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -11,6 +11,7 @@ export const CHAT_NOTES_JSON = 'chat.notes.json'; export const CHAT_PROMPTS_CSV = 'chat.prompts.csv'; export const GITHUB_PROMPTS_CSV_URL = 'https://raw.githubusercontent.com/f/awesome-chatgpt-prompts/main/prompts.csv'; +export const GITHUB_LOG_URL = 'https://raw.githubusercontent.com/lencx/ChatGPT/main/UPDATE_LOG.md'; export const DISABLE_AUTO_COMPLETE = { autoCapitalize: 'off', diff --git a/src/view/about/index.scss b/src/view/about/index.scss new file mode 100644 index 0000000..53cd372 --- /dev/null +++ b/src/view/about/index.scss @@ -0,0 +1,18 @@ +.about { + .log-tab { + font-size: 14px; + + h2 { + font-size: 16px; + } + } + + .about-tab { + .imgs { + img { + max-width: 200px; + margin-bottom: 20px; + } + } + } +} diff --git a/src/view/about/index.tsx b/src/view/about/index.tsx new file mode 100644 index 0000000..9a82e62 --- /dev/null +++ b/src/view/about/index.tsx @@ -0,0 +1,88 @@ +import { useState } from 'react'; +import { invoke } from '@tauri-apps/api'; +import { Tabs, Tag } from 'antd'; + +import { GITHUB_LOG_URL } from '@/utils'; +import useInit from '@/hooks/useInit'; +import Markdown from '@/components/Markdown'; +import './index.scss'; + +export default function About() { + const [logContent, setLogContent] = useState(''); + + useInit(async () => { + const data = (await invoke('get_data', { url: GITHUB_LOG_URL })) || ''; + setLogContent(data as string); + }); + + return ( +
+ }, + { label: 'Update Log', key: 'log', children: }, + ]} + /> +
+ ); +} + +const AboutChatGPT = () => { + return ( +
+ ChatGPT Desktop Application (Mac, Windows and Linux) +

+ 🕒 History versions:{' '} + + lencx/ChatGPT/releases + +

+

+ It is just a wrapper for the + + {' '} + OpenAI ChatGPT{' '} + + website, no other data transfer exists (you can check the{' '} + + {' '} + source code{' '} + + ). The development and maintenance of this software has taken up a lot of my time. If it + helps you, you can buy me a cup of coffee (Chinese users can use WeChat to scan the code), + thanks! +

+

+ + Buy Me A Coffee + {' '} +
+ +

+
+ ); +}; + +const LogTab = ({ content }: { content: string }) => { + return ( +
+

+ Ref:{' '} + + lencx/ChatGPT/UPDATE_LOG.md + +

+ +
+ ); +}; From f1e528d3a7199e20d53df2e19d84a356230b69f7 Mon Sep 17 00:00:00 2001 From: lencx Date: Tue, 24 Jan 2023 13:23:06 +0800 Subject: [PATCH 20/30] chore: dashboard --- src-tauri/src/app/cmd.rs | 14 +--- src-tauri/src/app/menu.rs | 6 +- src-tauri/src/app/setup.rs | 9 ++- src-tauri/src/app/window.rs | 57 +++++++++++++--- src-tauri/src/conf.rs | 12 +--- src-tauri/src/main.rs | 5 +- src/layout/index.tsx | 125 ++++++++++++++++++---------------- src/main.scss | 4 +- src/routes.tsx | 8 ++- src/view/dashboard/index.scss | 43 ++++++++++++ src/view/dashboard/index.tsx | 78 +++++++++++++++++++++ src/view/settings/General.tsx | 3 + 12 files changed, 269 insertions(+), 95 deletions(-) create mode 100644 src/view/dashboard/index.scss create mode 100644 src/view/dashboard/index.tsx diff --git a/src-tauri/src/app/cmd.rs b/src-tauri/src/app/cmd.rs index 0867256..0a4c655 100644 --- a/src-tauri/src/app/cmd.rs +++ b/src-tauri/src/app/cmd.rs @@ -140,16 +140,6 @@ pub fn parse_prompt(data: String) -> Vec { list } -#[command] -pub fn window_reload(app: AppHandle, label: &str) { - app - .app_handle() - .get_window(label) - .unwrap() - .eval("window.location.reload()") - .unwrap(); -} - #[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] pub struct ModelRecord { pub cmd: String, @@ -345,8 +335,8 @@ pub async fn sync_prompts(app: AppHandle, time: u64) -> Option> "Sync Prompts", "ChatGPT Prompts data has been synchronized!", ); - window_reload(app.clone(), "core"); - window_reload(app, "tray"); + window::window_reload(app.clone(), "core"); + window::window_reload(app, "tray"); return Some(data2); } diff --git a/src-tauri/src/app/menu.rs b/src-tauri/src/app/menu.rs index daaddcb..1556ebb 100644 --- a/src-tauri/src/app/menu.rs +++ b/src-tauri/src/app/menu.rs @@ -1,5 +1,5 @@ use crate::{ - app::{cmd, window}, + app::window, conf::{self, ChatConfJson}, utils, }; @@ -250,8 +250,8 @@ pub fn menu_handler(event: WindowMenuEvent) { .set_selected(popup_search) .unwrap(); ChatConfJson::amend(&serde_json::json!({ "popup_search": popup_search }), None).unwrap(); - cmd::window_reload(app.clone(), "core"); - cmd::window_reload(app, "tray"); + window::window_reload(app.clone(), "core"); + window::window_reload(app, "tray"); } "sync_prompts" => { tauri::api::dialog::ask( diff --git a/src-tauri/src/app/setup.rs b/src-tauri/src/app/setup.rs index a2a9bc0..54064b6 100644 --- a/src-tauri/src/app/setup.rs +++ b/src-tauri/src/app/setup.rs @@ -50,7 +50,12 @@ pub fn init(app: &mut App) -> std::result::Result<(), Box } else { let app = app.handle(); tauri::async_runtime::spawn(async move { - let mut main_win = WindowBuilder::new(&app, "core", WindowUrl::App(url.clone().into())) + let link = if chat_conf.dashboard { + "index.html" + } else { + &url + }; + let mut main_win = WindowBuilder::new(&app, "core", WindowUrl::App(link.into())) .title("ChatGPT") .resizable(true) .fullscreen(false) @@ -60,7 +65,7 @@ pub fn init(app: &mut App) -> std::result::Result<(), Box main_win = main_win.hidden_title(true); } - if url == "https://chat.openai.com" { + if url == "https://chat.openai.com" && !chat_conf.dashboard { main_win = main_win .initialization_script(include_str!("../vendors/floating-ui-core.js")) .initialization_script(include_str!("../vendors/floating-ui-dom.js")) diff --git a/src-tauri/src/app/window.rs b/src-tauri/src/app/window.rs index 7f4164e..f3f5e2e 100644 --- a/src-tauri/src/app/window.rs +++ b/src-tauri/src/app/window.rs @@ -97,14 +97,18 @@ pub fn control_window(handle: &tauri::AppHandle) { let app = handle.clone(); tauri::async_runtime::spawn(async move { if app.app_handle().get_window("main").is_none() { - WindowBuilder::new(&app, "main", WindowUrl::App("index.html".into())) - .title("Control Center") - .resizable(true) - .fullscreen(false) - .inner_size(1000.0, 700.0) - .min_inner_size(800.0, 600.0) - .build() - .unwrap(); + WindowBuilder::new( + &app, + "main", + WindowUrl::App("index.html?type=control".into()), + ) + .title("Control Center") + .resizable(true) + .fullscreen(false) + .inner_size(1000.0, 700.0) + .min_inner_size(800.0, 600.0) + .build() + .unwrap(); } else { let main_win = app.app_handle().get_window("main").unwrap(); main_win.show().unwrap(); @@ -112,3 +116,40 @@ pub fn control_window(handle: &tauri::AppHandle) { } }); } + +#[tauri::command] +pub async fn wa_window( + app: tauri::AppHandle, + label: String, + title: String, + url: String, + script: Option, +) { + info!("wa_window: {} :=> {}", title, url); + let win = app.get_window(&label); + if win.is_none() { + 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")) + .title(title) + .build() + .unwrap(); + }); + } else { + if !win.clone().unwrap().is_visible().unwrap() { + win.clone().unwrap().show().unwrap(); + } + win.unwrap().set_focus().unwrap(); + } +} + +#[tauri::command] +pub fn window_reload(app: tauri::AppHandle, label: &str) { + app + .app_handle() + .get_window(label) + .unwrap() + .eval("window.location.reload()") + .unwrap(); +} diff --git a/src-tauri/src/conf.rs b/src-tauri/src/conf.rs index 56b9ab0..c8455ce 100644 --- a/src-tauri/src/conf.rs +++ b/src-tauri/src/conf.rs @@ -18,6 +18,7 @@ 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 DEFAULT_CHAT_CONF: &str = r#"{ + "dashboard": false, "stay_on_top": false, "auto_update": "Prompt", "theme": "Light", @@ -33,6 +34,7 @@ pub const DEFAULT_CHAT_CONF: &str = r#"{ "ua_tray": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1" }"#; pub const DEFAULT_CHAT_CONF_MAC: &str = r#"{ + "dashboard": false, "stay_on_top": false, "auto_update": "Prompt", "theme": "Light", @@ -58,6 +60,7 @@ pub struct ChatConfJson { pub theme: String, // auto update policy, Prompt/Silent/Disable pub auto_update: String, + pub dashboard: bool, pub tray: bool, pub popup_search: bool, pub stay_on_top: bool, @@ -159,15 +162,6 @@ impl ChatConfJson { if let Some(handle) = app { tauri::api::process::restart(&handle.env()); - // tauri::api::dialog::ask( - // handle.get_window("core").as_ref(), - // "ChatGPT Restart", - // "Whether to restart immediately?", - // move |is_restart| { - // if is_restart { - // } - // }, - // ); } Ok(()) diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 2be1464..23ad286 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -7,7 +7,7 @@ mod app; mod conf; mod utils; -use app::{cmd, fs_extra, menu, setup}; +use app::{cmd, fs_extra, menu, setup, window}; use conf::ChatConfJson; use tauri::api::path; use tauri_plugin_autostart::MacosLauncher; @@ -73,13 +73,14 @@ async fn main() { cmd::parse_prompt, cmd::sync_prompts, cmd::sync_user_prompts, - cmd::window_reload, cmd::dalle2_window, cmd::cmd_list, cmd::download_list, cmd::get_download_list, cmd::get_data, fs_extra::metadata, + window::window_reload, + window::wa_window, ]) .setup(setup::init) .menu(menu::init()); diff --git a/src/layout/index.tsx b/src/layout/index.tsx index d6fcd97..b5bdf49 100644 --- a/src/layout/index.tsx +++ b/src/layout/index.tsx @@ -13,13 +13,18 @@ const { Content, Footer, Sider } = Layout; export default function ChatLayout() { const [collapsed, setCollapsed] = useState(false); + const [isDashboard, setDashboard] = useState(null); const [appInfo, setAppInfo] = useState>({}); const location = useLocation(); const [menuKey, setMenuKey] = useState(location.pathname); const go = useNavigate(); useEffect(() => { + if (location.search === '?type=control') { + go('/awesome'); + } setMenuKey(location.pathname); + setDashboard(location.pathname === '/'); }, [location.pathname]); useInit(async () => { @@ -36,69 +41,75 @@ export default function ChatLayout() { const isDark = appInfo.appTheme === 'dark'; + if (isDashboard === null) return null; + return ( - - setCollapsed(value)} - style={{ - overflow: 'auto', - height: '100vh', - position: 'fixed', - left: 0, - top: 0, - bottom: 0, - zIndex: 999, - }} - > -
- -
-
- {appInfo.appName} - - {appInfo.appVersion} - - - - - - -
- - go(i.key)} - /> - - - + ) : ( + + setCollapsed(value)} style={{ - overflow: 'inherit', + overflow: 'auto', + height: '100vh', + position: 'fixed', + left: 0, + top: 0, + bottom: 0, + zIndex: 999, }} > - - - +
+ +
+
+ {appInfo.appName} + + {appInfo.appVersion} + + + + + + +
+ + go(i.key)} + /> + + + + + + + - + )} ); } diff --git a/src/main.scss b/src/main.scss index f45ca3e..03f6aec 100644 --- a/src/main.scss +++ b/src/main.scss @@ -15,9 +15,11 @@ } html, -body { +body, +#root { padding: 0; margin: 0; + height: 100%; } .ant-table-selection-col, diff --git a/src/routes.tsx b/src/routes.tsx index f3e711f..bb4881a 100644 --- a/src/routes.tsx +++ b/src/routes.tsx @@ -22,6 +22,7 @@ import SyncRecord from '@/view/model/SyncRecord'; import Download from '@/view/download'; import Notes from '@/view/notes'; import Markdown from '@/view/markdown'; +import Dashboard from '@/view/dashboard'; export type ChatRouteMetaObject = { label: string; @@ -38,7 +39,7 @@ type ChatRouteObject = { export const routes: Array = [ { - path: '/', + path: '/awesome', element: , meta: { label: 'Awesome', @@ -121,6 +122,11 @@ export const routes: Array = [ icon: , }, }, + { + path: '/', + element: , + hideMenu: true, + }, ]; type MenuItem = Required['items'][number]; diff --git a/src/view/dashboard/index.scss b/src/view/dashboard/index.scss new file mode 100644 index 0000000..4387795 --- /dev/null +++ b/src/view/dashboard/index.scss @@ -0,0 +1,43 @@ +.dashboard { + position: fixed; + width: calc(100% - 30px); + height: calc(100% - 30px); + overflow-y: auto; + padding: 15px; + + &.dark { + background-color: #000; + } + + &.has-top-dom { + padding-top: 30px; + } + + .group-item { + margin-bottom: 20px; + + .title { + font-weight: bold; + font-size: 18px; + margin-bottom: 10px; + } + + .item { + .ant-card-body { + padding: 10px; + text-align: center; + font-weight: 500; + font-size: 14px; + } + + span { + display: block; + height: 100%; + width: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + } + } +} diff --git a/src/view/dashboard/index.tsx b/src/view/dashboard/index.tsx new file mode 100644 index 0000000..f6dc49e --- /dev/null +++ b/src/view/dashboard/index.tsx @@ -0,0 +1,78 @@ +import { useEffect, useState } from 'react'; +import clsx from 'clsx'; +import { Row, Col, Card } from 'antd'; +import { os, invoke } from '@tauri-apps/api'; + +import useInit from '@/hooks/useInit'; +import useJson from '@/hooks/useJson'; +import { CHAT_AWESOME_JSON, CHAT_CONF_JSON, readJSON } from '@/utils'; +import './index.scss'; + +export default function Dashboard() { + const { json } = useJson[]>(CHAT_AWESOME_JSON); + const [list, setList] = useState[]]>>([]); + const [hasClass, setClass] = useState(false); + const [theme, setTheme] = useState(''); + + useInit(async () => { + const getOS = await os.platform(); + const conf = await readJSON(CHAT_CONF_JSON); + const appTheme = await invoke('get_theme'); + setTheme(appTheme as string); + setClass(!conf?.titlebar && getOS === 'darwin'); + }); + + useEffect(() => { + const categories = new Map(); + + json?.forEach((i) => { + if (!i.enable) return; + if (!categories.has(i.category)) { + categories.set(i.category, []); + } + categories.get(i?.category).push(i); + }); + setList(Array.from(categories)); + }, [json?.length]); + + const handleLink = async (item: Record) => { + await invoke('wa_window', { + label: btoa(item.url).replace(/[^a-zA-Z0-9]/g, ''), + title: item.title, + url: item.url, + }); + }; + + return ( +
+
+ {list.map((i) => { + return ( +
+ + + {i[1].map((j, idx) => { + return ( +
+ handleLink(j)}> + {j?.title} + + + ); + })} + + + + ); + })} + + + ); +} diff --git a/src/view/settings/General.tsx b/src/view/settings/General.tsx index 6daddb8..4d47aad 100644 --- a/src/view/settings/General.tsx +++ b/src/view/settings/General.tsx @@ -60,6 +60,9 @@ export default function General() { return ( <> + + + From 4a7ee4dcf5fe4e0fc8ffd4c6df3d70523100824e Mon Sep 17 00:00:00 2001 From: lencx Date: Tue, 24 Jan 2023 16:24:49 +0800 Subject: [PATCH 21/30] fix: windows path (#242) --- src-tauri/src/utils.rs | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/src-tauri/src/utils.rs b/src-tauri/src/utils.rs index 9945bcd..8afe5bb 100644 --- a/src-tauri/src/utils.rs +++ b/src-tauri/src/utils.rs @@ -66,20 +66,33 @@ pub fn user_script() -> String { } pub fn open_file(path: PathBuf) { - info!("open_file: {}", path.to_string_lossy()); + let pathname = convert_path(path.to_str().unwrap()); + info!("open_file: {}", pathname); #[cfg(target_os = "macos")] - Command::new("open").arg("-R").arg(path).spawn().unwrap(); + Command::new("open") + .arg("-R") + .arg(pathname) + .spawn() + .unwrap(); #[cfg(target_os = "windows")] - Command::new("explorer") + Command::new("explorer.exe") .arg("/select,") - .arg(path) + .arg(pathname) .spawn() .unwrap(); // https://askubuntu.com/a/31071 #[cfg(target_os = "linux")] - Command::new("xdg-open").arg(path).spawn().unwrap(); + Command::new("xdg-open").arg(pathname).spawn().unwrap(); +} + +pub fn convert_path(path_str: &str) -> String { + if cfg!(target_os = "windows") { + path_str.replace('/', "\\") + } else { + String::from(path_str) + } } pub fn clear_conf(app: &tauri::AppHandle) { From ba438b0640b1a7f7c0b0ad6636291c4989908a67 Mon Sep 17 00:00:00 2001 From: lencx Date: Tue, 24 Jan 2023 18:34:31 +0800 Subject: [PATCH 22/30] chore: dashboard --- src-tauri/src/app/menu.rs | 4 +- src-tauri/src/app/setup.rs | 6 +- src-tauri/src/app/window.rs | 10 +-- src-tauri/src/conf.rs | 15 ++-- src-tauri/src/main.rs | 1 + src/components/SwitchOrigin/index.tsx | 102 +++++++++++++++++++------- src/view/awesome/index.tsx | 41 +++++++---- src/view/dashboard/index.scss | 24 ++++++ src/view/dashboard/index.tsx | 21 +++++- src/view/settings/General.tsx | 89 +++++++++++----------- src/view/settings/MainWindow.tsx | 2 +- src/view/settings/TrayWindow.tsx | 2 +- 12 files changed, 211 insertions(+), 106 deletions(-) diff --git a/src-tauri/src/app/menu.rs b/src-tauri/src/app/menu.rs index 1556ebb..9e1aece 100644 --- a/src-tauri/src/app/menu.rs +++ b/src-tauri/src/app/menu.rs @@ -235,7 +235,7 @@ pub fn menu_handler(event: WindowMenuEvent) { utils::run_check_update(app, false, None); } // Preferences - "control_center" => window::control_window(&app), + "control_center" => window::control_window(app.clone()), "restart" => tauri::api::process::restart(&app.env()), "inject_script" => open(&app, script_path), "go_conf" => utils::open_file(utils::chat_root()), @@ -431,7 +431,7 @@ pub fn tray_handler(handle: &AppHandle, event: SystemTrayEvent) { } } SystemTrayEvent::MenuItemClick { id, .. } => match id.as_str() { - "control_center" => window::control_window(&app), + "control_center" => window::control_window(app), "restart" => tauri::api::process::restart(&handle.env()), "show_dock_icon" => { ChatConfJson::amend(&serde_json::json!({ "hide_dock_icon": false }), Some(app)).unwrap(); diff --git a/src-tauri/src/app/setup.rs b/src-tauri/src/app/setup.rs index 54064b6..3aedf0f 100644 --- a/src-tauri/src/app/setup.rs +++ b/src-tauri/src/app/setup.rs @@ -6,7 +6,7 @@ use wry::application::accelerator::Accelerator; pub fn init(app: &mut App) -> std::result::Result<(), Box> { info!("stepup"); let chat_conf = ChatConfJson::get_chat_conf(); - let url = chat_conf.origin.to_string(); + let url = chat_conf.main_origin.to_string(); let theme = ChatConfJson::theme(); let handle = app.app_handle(); @@ -50,7 +50,7 @@ pub fn init(app: &mut App) -> std::result::Result<(), Box } else { let app = app.handle(); tauri::async_runtime::spawn(async move { - let link = if chat_conf.dashboard { + let link = if chat_conf.main_dashboard { "index.html" } else { &url @@ -65,7 +65,7 @@ pub fn init(app: &mut App) -> std::result::Result<(), Box main_win = main_win.hidden_title(true); } - if url == "https://chat.openai.com" && !chat_conf.dashboard { + if url == "https://chat.openai.com" && !chat_conf.main_dashboard { main_win = main_win .initialization_script(include_str!("../vendors/floating-ui-core.js")) .initialization_script(include_str!("../vendors/floating-ui-dom.js")) diff --git a/src-tauri/src/app/window.rs b/src-tauri/src/app/window.rs index f3f5e2e..8a912e3 100644 --- a/src-tauri/src/app/window.rs +++ b/src-tauri/src/app/window.rs @@ -93,12 +93,12 @@ pub fn dalle2_window( } } -pub fn control_window(handle: &tauri::AppHandle) { - let app = handle.clone(); +#[tauri::command] +pub fn control_window(handle: tauri::AppHandle) { tauri::async_runtime::spawn(async move { - if app.app_handle().get_window("main").is_none() { + if handle.get_window("main").is_none() { WindowBuilder::new( - &app, + &handle, "main", WindowUrl::App("index.html?type=control".into()), ) @@ -110,7 +110,7 @@ pub fn control_window(handle: &tauri::AppHandle) { .build() .unwrap(); } else { - let main_win = app.app_handle().get_window("main").unwrap(); + let main_win = handle.get_window("main").unwrap(); main_win.show().unwrap(); main_win.set_focus().unwrap(); } diff --git a/src-tauri/src/conf.rs b/src-tauri/src/conf.rs index c8455ce..e6832be 100644 --- a/src-tauri/src/conf.rs +++ b/src-tauri/src/conf.rs @@ -18,7 +18,6 @@ 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 DEFAULT_CHAT_CONF: &str = r#"{ - "dashboard": false, "stay_on_top": false, "auto_update": "Prompt", "theme": "Light", @@ -27,14 +26,15 @@ pub const DEFAULT_CHAT_CONF: &str = r#"{ "popup_search": false, "global_shortcut": "", "hide_dock_icon": false, - "origin": "https://chat.openai.com", + "main_dashboard": false, + "tray_dashboard": false, + "main_origin": "https://chat.openai.com", "tray_origin": "https://chat.openai.com", "default_origin": "https://chat.openai.com", "ua_window": "", "ua_tray": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1" }"#; pub const DEFAULT_CHAT_CONF_MAC: &str = r#"{ - "dashboard": false, "stay_on_top": false, "auto_update": "Prompt", "theme": "Light", @@ -43,7 +43,9 @@ pub const DEFAULT_CHAT_CONF_MAC: &str = r#"{ "popup_search": false, "global_shortcut": "", "hide_dock_icon": false, - "origin": "https://chat.openai.com", + "main_dashboard": false, + "tray_dashboard": false, + "main_origin": "https://chat.openai.com", "tray_origin": "https://chat.openai.com", "default_origin": "https://chat.openai.com", "ua_window": "", @@ -60,11 +62,12 @@ pub struct ChatConfJson { pub theme: String, // auto update policy, Prompt/Silent/Disable pub auto_update: String, - pub dashboard: bool, pub tray: bool, pub popup_search: bool, pub stay_on_top: bool, - pub origin: String, + pub main_dashboard: bool, + pub tray_dashboard: bool, + pub main_origin: String, pub tray_origin: String, pub default_origin: String, pub ua_window: String, diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 23ad286..71def99 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -81,6 +81,7 @@ async fn main() { fs_extra::metadata, window::window_reload, window::wa_window, + window::control_window, ]) .setup(setup::init) .menu(menu::init()); diff --git a/src/components/SwitchOrigin/index.tsx b/src/components/SwitchOrigin/index.tsx index 16e6dee..e07b87a 100644 --- a/src/components/SwitchOrigin/index.tsx +++ b/src/components/SwitchOrigin/index.tsx @@ -1,6 +1,6 @@ import { FC } from 'react'; import { Link } from 'react-router-dom'; -import { Form, Select, Tag, Tooltip } from 'antd'; +import { Form, Select, Tag, Tooltip, Switch } from 'antd'; import { QuestionCircleOutlined } from '@ant-design/icons'; import useJson from '@/hooks/useJson'; @@ -11,34 +11,82 @@ interface SwitchOriginProps { const SwitchOrigin: FC = ({ name }) => { const { json: list = [] } = useJson(CHAT_AWESOME_JSON); + const form = Form.useFormInstance(); + + const labelName = `(${name === 'main' ? 'Main' : 'SystemTray'})`; + const dashboardName = `${name}_dashboard`; + const originName = `${name}_origin`; + const isEnable = Form.useWatch(dashboardName, form); return ( - - Switch Origin ({name === 'origin' ? 'Main' : 'SystemTray'}){' '} - - If you need to set a new URL as the application loading window, please add the URL - in the Awesome menu and then select it. - - } - > - - - - } - name={name} - > - - + <> + + Dashboard {labelName}{' '} + +

+ Set the URL dashboard as an application window. +

+

+ If this is enabled, the Switch Origin {labelName}{' '} + setting will be invalid. +

+

+ If you want to add a new URL to the dashboard, add it in the{' '} + Awesome menu and make sure it is enabled. +

+ + } + > + +
+ + } + name={dashboardName} + valuePropName="checked" + > + +
+ + Switch Origin {labelName}{' '} + +

+ Set a single URL as an application window. +

+

+ If you need to set a new URL as the application loading window, please add the + URL in the Awesome menu and then select it. +

+ + } + > + +
+ + } + name={originName} + > + +
+ ); }; diff --git a/src/view/awesome/index.tsx b/src/view/awesome/index.tsx index 0b75c82..ba59b85 100644 --- a/src/view/awesome/index.tsx +++ b/src/view/awesome/index.tsx @@ -34,9 +34,7 @@ export default function Awesome() { updateJson(data); opInfo.resetRecord(); } - }, [opInfo.opType, - - formRef]); + }, [opInfo.opType, formRef]); const hide = () => { setVisible(false); @@ -54,18 +52,33 @@ export default function Awesome() { const handleOk = () => { formRef.current?.form?.validateFields().then(async (vals: Record) => { - if (opInfo.opType === 'new') { - const data = opAdd(vals); - await updateJson(data); - opInit(data); - message.success('Data added successfully'); + const idx = opData.findIndex((i) => i.url === vals.url); + if (idx === -1) { + if (opInfo.opType === 'new') { + const data = opAdd(vals); + await updateJson(data); + opInit(data); + message.success('Data added successfully'); + } + if (opInfo.opType === 'edit') { + const data = opReplace(opInfo?.opRecord?.[opSafeKey], vals); + await updateJson(data); + message.success('Data updated successfully'); + } + hide(); + } else { + const data = opData[idx]; + message.error( +
+
+ + {data.title}: {data.url} + +
+
This URL already exists, please edit it and try again.
+
, + ); } - if (opInfo.opType === 'edit') { - const data = opReplace(opInfo?.opRecord?.[opSafeKey], vals); - await updateJson(data); - message.success('Data updated successfully'); - } - hide(); }); }; diff --git a/src/view/dashboard/index.scss b/src/view/dashboard/index.scss index 4387795..7b66b9e 100644 --- a/src/view/dashboard/index.scss +++ b/src/view/dashboard/index.scss @@ -5,6 +5,30 @@ overflow-y: auto; padding: 15px; + &-no-data { + height: 100%; + display: flex; + align-items: center; + justify-content: center; + text-align: center; + flex-direction: column; + font-weight: bold; + + .icon { + color: #989898; + font-size: 24px; + } + + .txt { + font-size: 12px; + + a { + color: #1677ff; + cursor: pointer; + } + } + } + &.dark { background-color: #000; } diff --git a/src/view/dashboard/index.tsx b/src/view/dashboard/index.tsx index f6dc49e..00a68f0 100644 --- a/src/view/dashboard/index.tsx +++ b/src/view/dashboard/index.tsx @@ -1,6 +1,7 @@ import { useEffect, useState } from 'react'; import clsx from 'clsx'; import { Row, Col, Card } from 'antd'; +import { InboxOutlined } from '@ant-design/icons'; import { os, invoke } from '@tauri-apps/api'; import useInit from '@/hooks/useInit'; @@ -10,7 +11,7 @@ import './index.scss'; export default function Dashboard() { const { json } = useJson[]>(CHAT_AWESOME_JSON); - const [list, setList] = useState[]]>>([]); + const [list, setList] = useState[]]>>(); const [hasClass, setClass] = useState(false); const [theme, setTheme] = useState(''); @@ -23,6 +24,7 @@ export default function Dashboard() { }); useEffect(() => { + if (!json) return; const categories = new Map(); json?.forEach((i) => { @@ -43,6 +45,23 @@ export default function Dashboard() { }); }; + if (!list) return null; + if (list?.length === 0) { + return ( +
+
+ +
+ No data +
+ +
+ ); + } + return (
diff --git a/src/view/settings/General.tsx b/src/view/settings/General.tsx index 4d47aad..6f03a5f 100644 --- a/src/view/settings/General.tsx +++ b/src/view/settings/General.tsx @@ -6,6 +6,49 @@ import { platform } from '@tauri-apps/api/os'; import useInit from '@/hooks/useInit'; import { DISABLE_AUTO_COMPLETE } from '@/utils'; +export default function General() { + const [platformInfo, setPlatform] = useState(''); + + useInit(async () => { + setPlatform(await platform()); + }); + + return ( + <> + + + + {platformInfo === 'darwin' && ( + + + + )} + {platformInfo === 'darwin' && ( + + + + )} + + + Light + Dark + {['darwin', 'windows'].includes(platformInfo) && System} + + + } name="auto_update"> + + Prompt + Silent + {/*Disable*/} + + + } name="global_shortcut"> + + + + ); +} + const AutoUpdateLabel = () => { return ( @@ -50,49 +93,3 @@ const GlobalShortcutLabel = () => {
); }; - -export default function General() { - const [platformInfo, setPlatform] = useState(''); - - useInit(async () => { - setPlatform(await platform()); - }); - - return ( - <> - - - - - - - {platformInfo === 'darwin' && ( - - - - )} - {platformInfo === 'darwin' && ( - - - - )} - - - Light - Dark - {['darwin', 'windows'].includes(platformInfo) && System} - - - } name="auto_update"> - - Prompt - Silent - {/*Disable*/} - - - } name="global_shortcut"> - - - - ); -} diff --git a/src/view/settings/MainWindow.tsx b/src/view/settings/MainWindow.tsx index 18f7a6d..0235ab7 100644 --- a/src/view/settings/MainWindow.tsx +++ b/src/view/settings/MainWindow.tsx @@ -36,7 +36,7 @@ export default function General() { } name="popup_search" valuePropName="checked"> - + - + } name="ua_tray"> Date: Tue, 24 Jan 2023 23:23:52 +0800 Subject: [PATCH 23/30] chore: dashboard --- src-tauri/src/app/cmd.rs | 309 +------------------------- src-tauri/src/app/gpt.rs | 294 ++++++++++++++++++++++++ src-tauri/src/app/menu.rs | 3 +- src-tauri/src/app/mod.rs | 1 + src-tauri/src/app/window.rs | 19 +- src-tauri/src/main.rs | 26 +-- src-tauri/src/scripts/popup.core.js | 2 +- src/components/Markdown/index.scss | 6 - src/components/SwitchOrigin/index.tsx | 4 +- src/hooks/useData.ts | 17 +- src/layout/index.tsx | 5 +- src/main.scss | 6 + src/routes.tsx | 16 +- src/view/about/index.scss | 6 + src/view/awesome/Form.tsx | 6 +- src/view/awesome/index.tsx | 72 +++++- src/view/dashboard/index.scss | 6 + src/view/dashboard/index.tsx | 11 +- src/view/settings/index.tsx | 14 ++ 19 files changed, 473 insertions(+), 350 deletions(-) create mode 100644 src-tauri/src/app/gpt.rs diff --git a/src-tauri/src/app/cmd.rs b/src-tauri/src/app/cmd.rs index 0a4c655..64493cf 100644 --- a/src-tauri/src/app/cmd.rs +++ b/src-tauri/src/app/cmd.rs @@ -1,29 +1,16 @@ use crate::{ - app::{fs_extra, window}, - conf::{ChatConfJson, GITHUB_PROMPTS_CSV_URL}, + conf::ChatConfJson, utils::{self, chat_root, create_file}, }; use log::info; -use regex::Regex; -use std::{collections::HashMap, fs, path::PathBuf, vec}; +use std::{fs, path::PathBuf}; use tauri::{api, command, AppHandle, Manager, Theme}; -use walkdir::WalkDir; #[command] pub fn drag_window(app: AppHandle) { app.get_window("core").unwrap().start_dragging().unwrap(); } -#[command] -pub fn dalle2_window(app: AppHandle, query: String) { - window::dalle2_window( - &app.app_handle(), - Some(query), - Some("ChatGPT & DALL·E 2".to_string()), - None, - ); -} - #[command] pub fn fullscreen(app: AppHandle) { let win = app.get_window("core").unwrap(); @@ -61,13 +48,13 @@ pub fn get_chat_conf() -> ChatConfJson { } #[command] -pub fn get_theme() -> String { - ChatConfJson::theme().unwrap_or(Theme::Light).to_string() +pub fn reset_chat_conf() -> ChatConfJson { + ChatConfJson::reset_chat_conf() } #[command] -pub fn reset_chat_conf() -> ChatConfJson { - ChatConfJson::reset_chat_conf() +pub fn get_theme() -> String { + ChatConfJson::theme().unwrap_or(Theme::Light).to_string() } #[command] @@ -106,244 +93,6 @@ pub fn open_file(path: PathBuf) { utils::open_file(path); } -#[command] -pub fn get_chat_model_cmd() -> serde_json::Value { - let path = utils::chat_root().join("chat.model.cmd.json"); - let content = fs::read_to_string(path).unwrap_or_else(|_| r#"{"data":[]}"#.to_string()); - serde_json::from_str(&content).unwrap() -} - -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct PromptRecord { - pub cmd: Option, - pub act: String, - pub prompt: String, -} - -#[command] -pub fn parse_prompt(data: String) -> Vec { - let mut rdr = csv::Reader::from_reader(data.as_bytes()); - let mut list = vec![]; - for result in rdr.deserialize() { - let record: PromptRecord = result.unwrap_or_else(|err| { - info!("parse_prompt_error: {}", err); - PromptRecord { - cmd: None, - act: "".to_string(), - prompt: "".to_string(), - } - }); - if !record.act.is_empty() { - list.push(record); - } - } - list -} - -#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] -pub struct ModelRecord { - pub cmd: String, - pub act: String, - pub prompt: String, - pub tags: Vec, - pub enable: bool, -} - -#[command] -pub fn cmd_list() -> Vec { - let mut list = vec![]; - for entry in WalkDir::new(utils::chat_root().join("cache_model")) - .into_iter() - .filter_map(|e| e.ok()) - { - let file = fs::read_to_string(entry.path().display().to_string()); - if let Ok(v) = file { - let data: Vec = serde_json::from_str(&v).unwrap_or_else(|_| vec![]); - let enable_list = data.into_iter().filter(|v| v.enable); - list.extend(enable_list) - } - } - // dbg!(&list); - list.sort_by(|a, b| a.cmd.len().cmp(&b.cmd.len())); - list -} - -#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] -pub struct FileMetadata { - pub name: String, - pub ext: String, - pub created: u64, - pub id: String, -} - -#[tauri::command] -pub fn get_download_list(pathname: &str) -> (Vec, PathBuf) { - info!("get_download_list: {}", pathname); - let download_path = chat_root().join(PathBuf::from(pathname)); - let content = fs::read_to_string(&download_path).unwrap_or_else(|err| { - info!("download_list_error: {}", err); - fs::write(&download_path, "[]").unwrap(); - "[]".to_string() - }); - let list = serde_json::from_str::>(&content).unwrap_or_else(|err| { - info!("download_list_parse_error: {}", err); - vec![] - }); - - (list, download_path) -} - -#[command] -pub fn download_list(pathname: &str, dir: &str, filename: Option, id: Option) { - info!("download_list: {}", pathname); - let data = get_download_list(pathname); - let mut list = vec![]; - let mut idmap = HashMap::new(); - utils::vec_to_hashmap(data.0.into_iter(), "id", &mut idmap); - - for entry in WalkDir::new(utils::chat_root().join(dir)) - .into_iter() - .filter_entry(|e| !utils::is_hidden(e)) - .filter_map(|e| e.ok()) - { - let metadata = entry.metadata().unwrap(); - if metadata.is_file() { - let file_path = entry.path().display().to_string(); - let re = Regex::new(r"(?P[\d\w]+).(?P\w+)$").unwrap(); - let caps = re.captures(&file_path).unwrap(); - let fid = &caps["id"]; - let fext = &caps["ext"]; - - let mut file_data = FileMetadata { - name: fid.to_string(), - id: fid.to_string(), - ext: fext.to_string(), - created: fs_extra::system_time_to_ms(metadata.created()), - }; - - if idmap.get(fid).is_some() { - let name = idmap.get(fid).unwrap().get("name").unwrap().clone(); - match name { - serde_json::Value::String(v) => { - file_data.name = v.clone(); - v - } - _ => "".to_string(), - }; - } - - if filename.is_some() && id.is_some() { - if let Some(ref v) = id { - if fid == v { - if let Some(ref v2) = filename { - file_data.name = v2.to_string(); - } - } - } - } - list.push(serde_json::to_value(file_data).unwrap()); - } - } - - // dbg!(&list); - list.sort_by(|a, b| { - let a1 = a.get("created").unwrap().as_u64().unwrap(); - let b1 = b.get("created").unwrap().as_u64().unwrap(); - a1.cmp(&b1).reverse() - }); - - fs::write(data.1, serde_json::to_string_pretty(&list).unwrap()).unwrap(); -} - -#[command] -pub async fn sync_prompts(app: AppHandle, time: u64) -> Option> { - let res = utils::get_data(GITHUB_PROMPTS_CSV_URL, Some(&app)) - .await - .unwrap(); - - if let Some(v) = res { - let data = parse_prompt(v) - .iter() - .map(move |i| ModelRecord { - cmd: if i.cmd.is_some() { - i.cmd.clone().unwrap() - } else { - utils::gen_cmd(i.act.clone()) - }, - act: i.act.clone(), - prompt: i.prompt.clone(), - tags: vec!["chatgpt-prompts".to_string()], - enable: true, - }) - .collect::>(); - - let data2 = data.clone(); - - let model = utils::chat_root().join("chat.model.json"); - let model_cmd = utils::chat_root().join("chat.model.cmd.json"); - let chatgpt_prompts = utils::chat_root() - .join("cache_model") - .join("chatgpt_prompts.json"); - - if !utils::exists(&model) { - fs::write( - &model, - serde_json::json!({ - "name": "ChatGPT Model", - "link": "https://github.com/lencx/ChatGPT" - }) - .to_string(), - ) - .unwrap(); - } - - // chatgpt_prompts.json - fs::write( - chatgpt_prompts, - serde_json::to_string_pretty(&data).unwrap(), - ) - .unwrap(); - let cmd_data = cmd_list(); - - // chat.model.cmd.json - fs::write( - model_cmd, - serde_json::to_string_pretty(&serde_json::json!({ - "name": "ChatGPT CMD", - "last_updated": time, - "data": cmd_data, - })) - .unwrap(), - ) - .unwrap(); - let mut kv = HashMap::new(); - kv.insert( - "sync_prompts".to_string(), - serde_json::json!({ "id": "chatgpt_prompts", "last_updated": time }), - ); - let model_data = utils::merge( - &serde_json::from_str(&fs::read_to_string(&model).unwrap()).unwrap(), - &kv, - ); - - // chat.model.json - fs::write(model, serde_json::to_string_pretty(&model_data).unwrap()).unwrap(); - - // refresh window - api::dialog::message( - app.get_window("core").as_ref(), - "Sync Prompts", - "ChatGPT Prompts data has been synchronized!", - ); - window::window_reload(app.clone(), "core"); - window::window_reload(app, "tray"); - - return Some(data2); - } - - None -} - #[command] pub async fn get_data(app: AppHandle, url: String, is_msg: Option) -> Option { let is_msg = is_msg.unwrap_or(false); @@ -357,49 +106,3 @@ pub async fn get_data(app: AppHandle, url: String, is_msg: Option) -> Opti None }) } - -#[command] -pub async fn sync_user_prompts(url: String, data_type: String) -> Option> { - let res = utils::get_data(&url, None).await.unwrap_or_else(|err| { - info!("chatgpt_http_error: {}", err); - None - }); - - info!("chatgpt_http_url: {}", url); - - if let Some(v) = res { - let data; - if data_type == "csv" { - info!("chatgpt_http_csv_parse"); - data = parse_prompt(v); - } else if data_type == "json" { - info!("chatgpt_http_json_parse"); - data = serde_json::from_str(&v).unwrap_or_else(|err| { - info!("chatgpt_http_json_parse_error: {}", err); - vec![] - }); - } else { - info!("chatgpt_http_unknown_type"); - data = vec![]; - } - - let data = data - .iter() - .map(move |i| ModelRecord { - cmd: if i.cmd.is_some() { - i.cmd.clone().unwrap() - } else { - utils::gen_cmd(i.act.clone()) - }, - act: i.act.clone(), - prompt: i.prompt.clone(), - tags: vec!["user-sync".to_string()], - enable: true, - }) - .collect::>(); - - return Some(data); - } - - None -} diff --git a/src-tauri/src/app/gpt.rs b/src-tauri/src/app/gpt.rs new file mode 100644 index 0000000..392c15f --- /dev/null +++ b/src-tauri/src/app/gpt.rs @@ -0,0 +1,294 @@ +use crate::{ + app::{fs_extra, window}, + conf::GITHUB_PROMPTS_CSV_URL, + utils::{self, chat_root}, +}; +use log::info; +use regex::Regex; +use std::{collections::HashMap, fs, path::PathBuf, vec}; +use tauri::{api, command, AppHandle, Manager}; +use walkdir::WalkDir; + +#[command] +pub fn get_chat_model_cmd() -> serde_json::Value { + let path = utils::chat_root().join("chat.model.cmd.json"); + let content = fs::read_to_string(path).unwrap_or_else(|_| r#"{"data":[]}"#.to_string()); + serde_json::from_str(&content).unwrap() +} + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct PromptRecord { + pub cmd: Option, + pub act: String, + pub prompt: String, +} + +#[command] +pub fn parse_prompt(data: String) -> Vec { + let mut rdr = csv::Reader::from_reader(data.as_bytes()); + let mut list = vec![]; + for result in rdr.deserialize() { + let record: PromptRecord = result.unwrap_or_else(|err| { + info!("parse_prompt_error: {}", err); + PromptRecord { + cmd: None, + act: "".to_string(), + prompt: "".to_string(), + } + }); + if !record.act.is_empty() { + list.push(record); + } + } + list +} + +#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] +pub struct ModelRecord { + pub cmd: String, + pub act: String, + pub prompt: String, + pub tags: Vec, + pub enable: bool, +} + +#[command] +pub fn cmd_list() -> Vec { + let mut list = vec![]; + for entry in WalkDir::new(utils::chat_root().join("cache_model")) + .into_iter() + .filter_map(|e| e.ok()) + { + let file = fs::read_to_string(entry.path().display().to_string()); + if let Ok(v) = file { + let data: Vec = serde_json::from_str(&v).unwrap_or_else(|_| vec![]); + let enable_list = data.into_iter().filter(|v| v.enable); + list.extend(enable_list) + } + } + // dbg!(&list); + list.sort_by(|a, b| a.cmd.len().cmp(&b.cmd.len())); + list +} + +#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] +pub struct FileMetadata { + pub name: String, + pub ext: String, + pub created: u64, + pub id: String, +} + +#[tauri::command] +pub fn get_download_list(pathname: &str) -> (Vec, PathBuf) { + info!("get_download_list: {}", pathname); + let download_path = chat_root().join(PathBuf::from(pathname)); + let content = fs::read_to_string(&download_path).unwrap_or_else(|err| { + info!("download_list_error: {}", err); + fs::write(&download_path, "[]").unwrap(); + "[]".to_string() + }); + let list = serde_json::from_str::>(&content).unwrap_or_else(|err| { + info!("download_list_parse_error: {}", err); + vec![] + }); + + (list, download_path) +} + +#[command] +pub fn download_list(pathname: &str, dir: &str, filename: Option, id: Option) { + info!("download_list: {}", pathname); + let data = get_download_list(pathname); + let mut list = vec![]; + let mut idmap = HashMap::new(); + utils::vec_to_hashmap(data.0.into_iter(), "id", &mut idmap); + + for entry in WalkDir::new(utils::chat_root().join(dir)) + .into_iter() + .filter_entry(|e| !utils::is_hidden(e)) + .filter_map(|e| e.ok()) + { + let metadata = entry.metadata().unwrap(); + if metadata.is_file() { + let file_path = entry.path().display().to_string(); + let re = Regex::new(r"(?P[\d\w]+).(?P\w+)$").unwrap(); + let caps = re.captures(&file_path).unwrap(); + let fid = &caps["id"]; + let fext = &caps["ext"]; + + let mut file_data = FileMetadata { + name: fid.to_string(), + id: fid.to_string(), + ext: fext.to_string(), + created: fs_extra::system_time_to_ms(metadata.created()), + }; + + if idmap.get(fid).is_some() { + let name = idmap.get(fid).unwrap().get("name").unwrap().clone(); + match name { + serde_json::Value::String(v) => { + file_data.name = v.clone(); + v + } + _ => "".to_string(), + }; + } + + if filename.is_some() && id.is_some() { + if let Some(ref v) = id { + if fid == v { + if let Some(ref v2) = filename { + file_data.name = v2.to_string(); + } + } + } + } + list.push(serde_json::to_value(file_data).unwrap()); + } + } + + // dbg!(&list); + list.sort_by(|a, b| { + let a1 = a.get("created").unwrap().as_u64().unwrap(); + let b1 = b.get("created").unwrap().as_u64().unwrap(); + a1.cmp(&b1).reverse() + }); + + fs::write(data.1, serde_json::to_string_pretty(&list).unwrap()).unwrap(); +} + +#[command] +pub async fn sync_prompts(app: AppHandle, time: u64) -> Option> { + let res = utils::get_data(GITHUB_PROMPTS_CSV_URL, Some(&app)) + .await + .unwrap(); + + if let Some(v) = res { + let data = parse_prompt(v) + .iter() + .map(move |i| ModelRecord { + cmd: if i.cmd.is_some() { + i.cmd.clone().unwrap() + } else { + utils::gen_cmd(i.act.clone()) + }, + act: i.act.clone(), + prompt: i.prompt.clone(), + tags: vec!["chatgpt-prompts".to_string()], + enable: true, + }) + .collect::>(); + + let data2 = data.clone(); + + let model = utils::chat_root().join("chat.model.json"); + let model_cmd = utils::chat_root().join("chat.model.cmd.json"); + let chatgpt_prompts = utils::chat_root() + .join("cache_model") + .join("chatgpt_prompts.json"); + + if !utils::exists(&model) { + fs::write( + &model, + serde_json::json!({ + "name": "ChatGPT Model", + "link": "https://github.com/lencx/ChatGPT" + }) + .to_string(), + ) + .unwrap(); + } + + // chatgpt_prompts.json + fs::write( + chatgpt_prompts, + serde_json::to_string_pretty(&data).unwrap(), + ) + .unwrap(); + let cmd_data = cmd_list(); + + // chat.model.cmd.json + fs::write( + model_cmd, + serde_json::to_string_pretty(&serde_json::json!({ + "name": "ChatGPT CMD", + "last_updated": time, + "data": cmd_data, + })) + .unwrap(), + ) + .unwrap(); + let mut kv = HashMap::new(); + kv.insert( + "sync_prompts".to_string(), + serde_json::json!({ "id": "chatgpt_prompts", "last_updated": time }), + ); + let model_data = utils::merge( + &serde_json::from_str(&fs::read_to_string(&model).unwrap()).unwrap(), + &kv, + ); + + // chat.model.json + fs::write(model, serde_json::to_string_pretty(&model_data).unwrap()).unwrap(); + + // refresh window + api::dialog::message( + app.get_window("core").as_ref(), + "Sync Prompts", + "ChatGPT Prompts data has been synchronized!", + ); + window::window_reload(app.clone(), "core"); + window::window_reload(app, "tray"); + + return Some(data2); + } + + None +} + +#[command] +pub async fn sync_user_prompts(url: String, data_type: String) -> Option> { + let res = utils::get_data(&url, None).await.unwrap_or_else(|err| { + info!("chatgpt_http_error: {}", err); + None + }); + + info!("chatgpt_http_url: {}", url); + + if let Some(v) = res { + let data; + if data_type == "csv" { + info!("chatgpt_http_csv_parse"); + data = parse_prompt(v); + } else if data_type == "json" { + info!("chatgpt_http_json_parse"); + data = serde_json::from_str(&v).unwrap_or_else(|err| { + info!("chatgpt_http_json_parse_error: {}", err); + vec![] + }); + } else { + info!("chatgpt_http_unknown_type"); + data = vec![]; + } + + let data = data + .iter() + .map(move |i| ModelRecord { + cmd: if i.cmd.is_some() { + i.cmd.clone().unwrap() + } else { + utils::gen_cmd(i.act.clone()) + }, + act: i.act.clone(), + prompt: i.prompt.clone(), + tags: vec!["user-sync".to_string()], + enable: true, + }) + .collect::>(); + + return Some(data); + } + + None +} diff --git a/src-tauri/src/app/menu.rs b/src-tauri/src/app/menu.rs index 9e1aece..e507613 100644 --- a/src-tauri/src/app/menu.rs +++ b/src-tauri/src/app/menu.rs @@ -235,7 +235,7 @@ pub fn menu_handler(event: WindowMenuEvent) { utils::run_check_update(app, false, None); } // Preferences - "control_center" => window::control_window(app.clone()), + "control_center" => window::control_window(app), "restart" => tauri::api::process::restart(&app.env()), "inject_script" => open(&app, script_path), "go_conf" => utils::open_file(utils::chat_root()), @@ -340,7 +340,6 @@ pub fn menu_handler(event: WindowMenuEvent) { "reload" => win.eval("window.location.reload()").unwrap(), "go_back" => win.eval("window.history.go(-1)").unwrap(), "go_forward" => win.eval("window.history.go(1)").unwrap(), - // core: document.querySelector('main .overflow-y-auto') "scroll_top" => win .eval( r#"window.scroll({ diff --git a/src-tauri/src/app/mod.rs b/src-tauri/src/app/mod.rs index 46d47f9..39fac31 100644 --- a/src-tauri/src/app/mod.rs +++ b/src-tauri/src/app/mod.rs @@ -1,5 +1,6 @@ pub mod cmd; pub mod fs_extra; +pub mod gpt; pub mod menu; pub mod setup; pub mod window; diff --git a/src-tauri/src/app/window.rs b/src-tauri/src/app/window.rs index 8a912e3..e23daa8 100644 --- a/src-tauri/src/app/window.rs +++ b/src-tauri/src/app/window.rs @@ -93,6 +93,16 @@ pub fn dalle2_window( } } +#[tauri::command] +pub fn dalle2_search_window(app: tauri::AppHandle, query: String) { + dalle2_window( + &app.app_handle(), + Some(query), + Some("ChatGPT & DALL·E 2".to_string()), + None, + ); +} + #[tauri::command] pub fn control_window(handle: tauri::AppHandle) { tauri::async_runtime::spawn(async move { @@ -105,8 +115,8 @@ pub fn control_window(handle: tauri::AppHandle) { .title("Control Center") .resizable(true) .fullscreen(false) - .inner_size(1000.0, 700.0) - .min_inner_size(800.0, 600.0) + .inner_size(1200.0, 700.0) + .min_inner_size(1000.0, 600.0) .build() .unwrap(); } else { @@ -140,6 +150,11 @@ pub async fn wa_window( if !win.clone().unwrap().is_visible().unwrap() { win.clone().unwrap().show().unwrap(); } + win + .clone() + .unwrap() + .eval("window.location.reload()") + .unwrap(); win.unwrap().set_focus().unwrap(); } } diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 71def99..601d4dc 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -7,7 +7,7 @@ mod app; mod conf; mod utils; -use app::{cmd, fs_extra, menu, setup, window}; +use app::{cmd, fs_extra, gpt, menu, setup, window}; use conf::ChatConfJson; use tauri::api::path; use tauri_plugin_autostart::MacosLauncher; @@ -30,8 +30,8 @@ async fn main() { trace: Color::Cyan, }; - cmd::download_list("chat.download.json", "download", None, None); - cmd::download_list("chat.notes.json", "notes", None, None); + gpt::download_list("chat.download.json", "download", None, None); + gpt::download_list("chat.notes.json", "notes", None, None); let chat_conf = ChatConfJson::get_chat_conf(); @@ -69,19 +69,19 @@ async fn main() { cmd::form_confirm, cmd::form_msg, cmd::open_file, - cmd::get_chat_model_cmd, - cmd::parse_prompt, - cmd::sync_prompts, - cmd::sync_user_prompts, - cmd::dalle2_window, - cmd::cmd_list, - cmd::download_list, - cmd::get_download_list, cmd::get_data, - fs_extra::metadata, - window::window_reload, + gpt::get_chat_model_cmd, + gpt::parse_prompt, + gpt::sync_prompts, + gpt::sync_user_prompts, + gpt::cmd_list, + gpt::download_list, + gpt::get_download_list, window::wa_window, window::control_window, + window::window_reload, + window::dalle2_search_window, + fs_extra::metadata, ]) .setup(setup::init) .menu(menu::init()); diff --git a/src-tauri/src/scripts/popup.core.js b/src-tauri/src/scripts/popup.core.js index f45db80..3cbbe18 100644 --- a/src-tauri/src/scripts/popup.core.js +++ b/src-tauri/src/scripts/popup.core.js @@ -33,7 +33,7 @@ async function init() { document.body.addEventListener('mousedown', async (e) => { selectionMenu.style.display = 'none'; if (e.target.id === 'chagpt-selection-menu') { - await invoke('dalle2_window', { query: encodeURIComponent(window.__DALLE2_CONTENT__) }); + await invoke('dalle2_search_window', { query: encodeURIComponent(window.__DALLE2_CONTENT__) }); } else { delete window.__DALLE2_CONTENT__; } diff --git a/src/components/Markdown/index.scss b/src/components/Markdown/index.scss index 64745c0..5c0bc57 100644 --- a/src/components/Markdown/index.scss +++ b/src/components/Markdown/index.scss @@ -13,12 +13,6 @@ code { font-family: monospace, monospace; } - - code { - background-color: rgba(200, 200, 200, 0.4); - padding: 2px 4px; - border-radius: 5px; - } } .md-main { diff --git a/src/components/SwitchOrigin/index.tsx b/src/components/SwitchOrigin/index.tsx index e07b87a..d51c510 100644 --- a/src/components/SwitchOrigin/index.tsx +++ b/src/components/SwitchOrigin/index.tsx @@ -28,7 +28,7 @@ const SwitchOrigin: FC = ({ name }) => { title={

- Set the URL dashboard as an application window. + Set Dashboard as the application default window.

If this is enabled, the Switch Origin {labelName}{' '} @@ -58,7 +58,7 @@ const SwitchOrigin: FC = ({ name }) => { title={

- Set a single URL as an application window. + Set a single URL as the application default window.

If you need to set a new URL as the application loading window, please add the diff --git a/src/hooks/useData.ts b/src/hooks/useData.ts index de82cce..3a58498 100644 --- a/src/hooks/useData.ts +++ b/src/hooks/useData.ts @@ -28,6 +28,12 @@ export default function useData(oData: any[]) { return nData; }; + const opRemoveItems = (ids: string[]) => { + const nData = opData.filter((i) => !ids.includes(i[safeKey])); + setData(nData); + return nData; + }; + const opReplace = (id: string, data: any) => { const nData = [...opData]; const idx = opData.findIndex((v) => v[safeKey] === id); @@ -51,5 +57,14 @@ export default function useData(oData: any[]) { return nData; }; - return { opSafeKey: safeKey, opInit, opReplace, opAdd, opRemove, opData, opReplaceItems }; + return { + opSafeKey: safeKey, + opInit, + opReplace, + opAdd, + opRemove, + opRemoveItems, + opData, + opReplaceItems, + }; } diff --git a/src/layout/index.tsx b/src/layout/index.tsx index b5bdf49..f1a331d 100644 --- a/src/layout/index.tsx +++ b/src/layout/index.tsx @@ -21,7 +21,10 @@ export default function ChatLayout() { useEffect(() => { if (location.search === '?type=control') { - go('/awesome'); + go('/settings'); + } + if (location.search === '?type=preview') { + go('/?type=preview'); } setMenuKey(location.pathname); setDashboard(location.pathname === '/'); diff --git a/src/main.scss b/src/main.scss index 03f6aec..c7c6441 100644 --- a/src/main.scss +++ b/src/main.scss @@ -103,3 +103,9 @@ body, .chatico { cursor: pointer; } + +.awesome-tips { + .ant-tag { + cursor: pointer; + } +} diff --git a/src/routes.tsx b/src/routes.tsx index bb4881a..66910fb 100644 --- a/src/routes.tsx +++ b/src/routes.tsx @@ -38,6 +38,14 @@ type ChatRouteObject = { }; export const routes: Array = [ + { + path: '/settings', + element: , + meta: { + label: 'Settings', + icon: , + }, + }, { path: '/awesome', element: , @@ -106,14 +114,6 @@ export const routes: Array = [ icon: , }, }, - { - path: '/settings', - element: , - meta: { - label: 'Settings', - icon: , - }, - }, { path: '/about', element: , diff --git a/src/view/about/index.scss b/src/view/about/index.scss index 53cd372..408924f 100644 --- a/src/view/about/index.scss +++ b/src/view/about/index.scss @@ -15,4 +15,10 @@ } } } + + code { + background-color: rgba(200, 200, 200, 0.4); + padding: 2px 4px; + border-radius: 5px; + } } diff --git a/src/view/awesome/Form.tsx b/src/view/awesome/Form.tsx index 89054ce..baee30b 100644 --- a/src/view/awesome/Form.tsx +++ b/src/view/awesome/Form.tsx @@ -43,7 +43,11 @@ const AwesomeForm: ForwardRefRenderFunction = ({ re > - + diff --git a/src/view/awesome/index.tsx b/src/view/awesome/index.tsx index ba59b85..654cc1a 100644 --- a/src/view/awesome/index.tsx +++ b/src/view/awesome/index.tsx @@ -1,5 +1,8 @@ import { useRef, useEffect, useState } from 'react'; -import { Table, Modal, Popconfirm, Button, message } from 'antd'; +import { Link, useNavigate } from 'react-router-dom'; +import { Table, Modal, Popconfirm, Button, Tooltip, Tag, message } from 'antd'; +import { QuestionCircleOutlined } from '@ant-design/icons'; +import { invoke } from '@tauri-apps/api'; import useJson from '@/hooks/useJson'; import useData from '@/hooks/useData'; @@ -13,9 +16,10 @@ import AwesomeForm from './Form'; export default function Awesome() { const formRef = useRef(null); const [isVisible, setVisible] = useState(false); - const { opData, opInit, opAdd, opReplace, opReplaceItems, opRemove, opSafeKey } = useData([]); + const { opData, opInit, opAdd, opReplace, opReplaceItems, opRemove, opRemoveItems, opSafeKey } = + useData([]); const { columns, ...opInfo } = useColumns(awesomeColumns()); - const { rowSelection, selectedRowIDs } = useTableRowSelection(); + const { rowSelection, selectedRowIDs, rowReset } = useTableRowSelection(); const { json, updateJson } = useJson(CHAT_AWESOME_JSON); const selectedItems = rowSelection.selectedRowKeys || []; @@ -48,11 +52,19 @@ export default function Awesome() { } }, [opInfo.opTime]); - const handleDelete = () => {}; + const handleDelete = () => { + const data = opRemoveItems(selectedRowIDs); + updateJson(data); + rowReset(); + message.success('All selected URLs have been deleted'); + }; const handleOk = () => { formRef.current?.form?.validateFields().then(async (vals: Record) => { - const idx = opData.findIndex((i) => i.url === vals.url); + let idx = opData.findIndex((i) => i.url === vals.url); + if (vals.url === opInfo?.opRecord?.url) { + idx = -1; + } if (idx === -1) { if (opInfo.opType === 'new') { const data = opAdd(vals); @@ -87,14 +99,28 @@ export default function Awesome() { updateJson(data); }; + const handlePreview = () => { + invoke('wa_window', { + label: 'awesome_preview', + url: 'index.html?type=preview', + title: 'Preview Dashboard', + }); + }; + const modalTitle = `${{ new: 'Create', edit: 'Edit' }[opInfo.opType]} URL`; return (

- +
+ + + +
{selectedItems.length > 0 && ( <> @@ -139,3 +165,33 @@ export default function Awesome() {
); } + +const PreviewTip = () => { + const go = useNavigate(); + const handleGo = (v: string) => { + go(`/settings?type=${v}`); + }; + + return ( + + Click the button to preview, and in + Settings + you can set a single URL or Dashboard as the default window for the app. +
+ handleGo('main_window')} color="blue"> + Main Window + + {'or '} + handleGo('tray_window')} color="blue"> + SystemTray Window + +
+ } + > + + + ); +}; diff --git a/src/view/dashboard/index.scss b/src/view/dashboard/index.scss index 7b66b9e..96c8a7a 100644 --- a/src/view/dashboard/index.scss +++ b/src/view/dashboard/index.scss @@ -20,7 +20,9 @@ } .txt { + padding: 10px; font-size: 12px; + line-height: 16px; a { color: #1677ff; @@ -37,6 +39,10 @@ padding-top: 30px; } + &.preview { + padding-top: 15px; + } + .group-item { margin-bottom: 20px; diff --git a/src/view/dashboard/index.tsx b/src/view/dashboard/index.tsx index 00a68f0..1b97d41 100644 --- a/src/view/dashboard/index.tsx +++ b/src/view/dashboard/index.tsx @@ -1,5 +1,6 @@ import { useEffect, useState } from 'react'; import clsx from 'clsx'; +import { useSearchParams } from 'react-router-dom'; import { Row, Col, Card } from 'antd'; import { InboxOutlined } from '@ant-design/icons'; import { os, invoke } from '@tauri-apps/api'; @@ -10,6 +11,7 @@ import { CHAT_AWESOME_JSON, CHAT_CONF_JSON, readJSON } from '@/utils'; import './index.scss'; export default function Dashboard() { + const [params] = useSearchParams(); const { json } = useJson[]>(CHAT_AWESOME_JSON); const [list, setList] = useState[]]>>(); const [hasClass, setClass] = useState(false); @@ -56,14 +58,19 @@ export default function Dashboard() {
Go to invoke('control_window')}>{'Control Center -> Awesome'} to add - data + data and make sure they are enabled.
); } return ( -
+
{list.map((i) => { return ( diff --git a/src/view/settings/index.tsx b/src/view/settings/index.tsx index 6d7ce83..1b41f15 100644 --- a/src/view/settings/index.tsx +++ b/src/view/settings/index.tsx @@ -1,4 +1,5 @@ import { useEffect, useState } from 'react'; +import { useSearchParams } from 'react-router-dom'; import { Form, Tabs, Space, Button, Popconfirm, message } from 'antd'; import { invoke, dialog, process, path, shell } from '@tauri-apps/api'; import { clone, omit, isEqual } from 'lodash'; @@ -11,9 +12,16 @@ import MainWindow from './MainWindow'; import TrayWindow from './TrayWindow'; export default function Settings() { + const [params] = useSearchParams(); + const [activeKey, setActiveKey] = useState('general'); const [form] = Form.useForm(); const [chatConf, setChatConf] = useState(null); const [filePath, setPath] = useState(''); + const key = params.get('type'); + + useEffect(() => { + setActiveKey(key ? key : 'general'); + }, [key]); useInit(async () => { setChatConf(await invoke('get_chat_conf')); @@ -55,6 +63,10 @@ export default function Settings() { } }; + const handleTab = (v: string) => { + setActiveKey(v); + }; + return (
@@ -66,6 +78,8 @@ export default function Settings() { wrapperCol={{ span: 13, offset: 1 }} > }, { label: 'Main Window', key: 'main_window', children: }, From 7d602e01a9c74f2d2b646c18e293ee0e01ff9423 Mon Sep 17 00:00:00 2001 From: lencx Date: Wed, 25 Jan 2023 00:26:40 +0800 Subject: [PATCH 24/30] update log --- UPDATE_LOG.md | 161 +++++++++++++++++++++++++++----------------------- 1 file changed, 86 insertions(+), 75 deletions(-) diff --git a/UPDATE_LOG.md b/UPDATE_LOG.md index 191a5ce..ce685ae 100644 --- a/UPDATE_LOG.md +++ b/UPDATE_LOG.md @@ -1,197 +1,208 @@ # UPDATE LOG +## v0.10.0 + +Fix: + +- After exporting the file, open an empty file explorer (https://github.com/lencx/ChatGPT/issues/242) + +Feat: + +- Markdown files support editing and live preview +- Add `Awesome` menu to the `Control Center` (similar to bookmarks, but it's just a start, more possibilities in the future), custom URL support for the home and tray windows (if you're tired of ChatGPT as your home screen). + ## v0.9.2 -fix: slash command does not work +Fix: Slash command does not work ## v0.9.1 -fix: slash command does not work +Fix: Slash command does not work ## v0.9.0 -fix: +Fix: -- export button does not work +- Export button does not work -feat: +Feat: -- add an export markdown button +- Add an export markdown button - `Control Center` adds `Notes` and `Download` menus for managing exported chat files (Markdown, PNG, PDF). `Notes` supports markdown previews. ## v0.8.1 -fix: +Fix: -- export button keeps blinking -- export button in the old chat does not work -- disable export sharing links because it is a security risk +- Export button keeps blinking +- Export button in the old chat does not work +- Disable export sharing links because it is a security risk ## v0.8.0 -feat: +Feat: -- theme enhancement (Light, Dark, System) -- automatic updates support `silent` settings -- pop-up search: select the ChatGPT content with the mouse, the `DALL·E 2` button appears, and click to jump (note: because the search content filled by the script cannot trigger the event directly, you need to enter a space in the input box to make the button clickable). +- Theme enhancement (Light, Dark, System) +- Automatic updates support `silent` settings +- Pop-up search: select the ChatGPT content with the mouse, the `DALL·E 2` button appears, and click to jump (note: because the search content filled by the script cannot trigger the event directly, you need to enter a space in the input box to make the button clickable). -fix: +Fix: -- close the main window and hide it in the tray (windows systems) +- Close the main window and hide it in the tray (windows systems) ## v0.7.4 -fix: +Fix: -- trying to resolve linux errors: `error while loading shared libraries` -- customize global shortcuts (`Menu -> Preferences -> Control Center -> General -> Global Shortcut`) +- Trying to resolve linux errors: `error while loading shared libraries` +- Customize global shortcuts (`Menu -> Preferences -> Control Center -> General -> Global Shortcut`) ## v0.7.3 -chore: +Chore: -- optimize slash command style -- optimize tray menu icon and button icons -- global shortcuts to the chatgpt app (mac: `Command + Shift + O`, windows: `Ctrl + Shift + O`) +- Optimize slash command style +- Optimize tray menu icon and button icons +- Global shortcuts to the chatgpt app (mac: `Command + Shift + O`, windows: `Ctrl + Shift + O`) ## v0.7.2 -fix: some windows systems cannot start the application +Fix: Some windows systems cannot start the application ## v0.7.1 -fix: +Fix: -- some windows systems cannot start the application -- windows and linux add about menu (show version information) -- the tray icon is indistinguishable from the background in dark mode on window and linux +- Some windows systems cannot start the application +- Windows and linux add about menu (show version information) +- The tray icon is indistinguishable from the background in dark mode on window and linux ## v0.7.0 -fix: +Fix: -- mac m1 copy/paste does not work on some system versions -- optimize the save chat log button to a small icon, the tray window no longer provides a save chat log button (the buttons causes the input area to become larger and the content area to become smaller) +- Mac m1 copy/paste does not work on some system versions +- Optimize the save chat log button to a small icon, the tray window no longer provides a save chat log button (the buttons causes the input area to become larger and the content area to become smaller) -feat: +Feat: -- use the keyboard `⇧` (arrow up) and `⇩` (arrow down) keys to select the slash command +- Use the keyboard `⇧` (arrow up) and `⇩` (arrow down) keys to select the slash command ## v0.6.10 -fix: sync failure on windows +Fix: Sync failure on windows ## v0.6.4 -fix: path not allowed on the configured scope +Fix: Path not allowed on the configured scope -feat: +Feat: -- optimize the generated pdf file size -- menu added `Sync Prompts` +- Optimize the generated pdf file size +- Menu added `Sync Prompts` - `Control Center` added `Sync Custom` -- the slash command is triggered by the enter key -- under the slash command, use the tab key to modify the contents of the `{q}` tag (only single changes are supported (https://github.com/lencx/ChatGPT/issues/54) +- The slash command is triggered by the enter key +- Under the slash command, use the tab key to modify the contents of the `{q}` tag (only single changes are supported (https://github.com/lencx/ChatGPT/issues/54) ## v0.6.0 -fix: +Fix: -- windows show Chinese when upgrading +- Windows show Chinese when upgrading ## v0.5.1 -some optimization +Some optimization ## v0.5.0 -feat: `Control Center` added `chatgpt-prompts` synchronization +Feat: `Control Center` added `chatgpt-prompts` synchronization ## v0.4.2 -add chatgpt log (path: `~/.chatgpt/chatgpt.log`) +Add chatgpt log (path: `~/.chatgpt/chatgpt.log`) ## v0.4.1 -fix: +Fix: -- tray window style optimization +- Tray window style optimization ## v0.4.0 -feat: +Feat: -- customize the ChatGPT prompts command (https://github.com/lencx/ChatGPT#-announcement) -- menu enhancement: hide application icons from the Dock (support macOS only) +- Customize the ChatGPT prompts command (https://github.com/lencx/ChatGPT#-announcement) +- Menu enhancement: hide application icons from the Dock (support macOS only) ## v0.3.0 -fix: can't open ChatGPT +Fix: Can't open ChatGPT -feat: menu enhancement +Feat: Menu enhancement -- the control center of ChatGPT application -- open the configuration file directory +- The control center of ChatGPT application +- Open the configuration file directory ## v0.2.2 -feat: +Feat: -- menu: go to config +- Menu: go to config ## v0.2.1 -feat: menu optimization +Feat: Menu optimization ## v0.2.0 -feat: menu enhancement +Feat: Menu enhancement -- customize user-agent to prevent security detection interception -- clear all chatgpt configuration files +- Customize user-agent to prevent security detection interception +- Clear all chatgpt configuration files ## v0.1.8 -feat: +Feat: -- menu enhancement: theme, titlebar -- modify website address +- Menu enhancement: theme, titlebar +- Modify website address ## v0.1.7 -feat: tray window +Feat: Tray window ## v0.1.6 -feat: +Feat: -- stay on top -- export ChatGPT history +- Stay on top +- Export ChatGPT history ## v0.1.5 -fix: mac can't use shortcut keys +Fix: Mac can't use shortcut keys ## v0.1.4 -feat: +Feat: -- beautify icons -- add system tray menu +- Beautify icons +- Add system tray menu ## v0.1.3 -fix: only mac supports `TitleBarStyle` +Fix: Only mac supports `TitleBarStyle` ## v0.1.2 -initialization +Initialization ## v0.1.1 -initialization +Initialization ## v0.1.0 -initialization +Initialization From 9a5c008a259188632dd03982e086c4d58f540cfc Mon Sep 17 00:00:00 2001 From: lencx Date: Wed, 25 Jan 2023 00:38:36 +0800 Subject: [PATCH 25/30] chore: script --- src-tauri/src/scripts/core.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src-tauri/src/scripts/core.js b/src-tauri/src/scripts/core.js index 74a6f4a..dd930b1 100644 --- a/src-tauri/src/scripts/core.js +++ b/src-tauri/src/scripts/core.js @@ -64,12 +64,14 @@ async function init() { topDom.id = "chatgpt-app-window-top"; document.body.appendChild(topDom); - const nav = document.body.querySelector('nav'); - if (nav) { - const currentPaddingTop = parseInt(window.getComputedStyle(document.querySelector('nav'), null).getPropertyValue('padding-top').replace('px', ''), 10); - const navStyleDom = document.createElement("style"); - navStyleDom.innerHTML = `nav{padding-top:${currentPaddingTop + topDom.clientHeight}px !important}`; - document.head.appendChild(navStyleDom); + if (window.location.host === 'chat.openai.com') { + const nav = document.body.querySelector('nav'); + if (nav) { + const currentPaddingTop = parseInt(window.getComputedStyle(document.querySelector('nav'), null).getPropertyValue('padding-top').replace('px', ''), 10); + const navStyleDom = document.createElement("style"); + navStyleDom.innerHTML = `nav{padding-top:${currentPaddingTop + topDom.clientHeight}px !important}`; + document.head.appendChild(navStyleDom); + } } topDom.addEventListener("mousedown", () => invoke("drag_window")); From 3fea94f669134384581cfc936c002e95700fd0de Mon Sep 17 00:00:00 2001 From: lencx Date: Wed, 25 Jan 2023 00:50:58 +0800 Subject: [PATCH 26/30] chore: markdown style --- package.json | 1 + src/components/Markdown/index.tsx | 1 + 2 files changed, 2 insertions(+) diff --git a/package.json b/package.json index 387d4e8..2aece26 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "antd": "^5.1.0", "clsx": "^1.2.1", "dayjs": "^1.11.7", + "github-markdown-css": "^5.1.0", "lodash": "^4.17.21", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/src/components/Markdown/index.tsx b/src/components/Markdown/index.tsx index c77bc0b..785a8f5 100644 --- a/src/components/Markdown/index.tsx +++ b/src/components/Markdown/index.tsx @@ -5,6 +5,7 @@ import remarkGfm from 'remark-gfm'; import rehypeRaw from 'rehype-raw'; import SyntaxHighlighter from 'react-syntax-highlighter'; import agate from 'react-syntax-highlighter/dist/esm/styles/hljs/agate'; +import 'github-markdown-css'; import './index.scss'; From 221287bbd50dab2f315754b0fa973b5d790ea702 Mon Sep 17 00:00:00 2001 From: lencx Date: Wed, 25 Jan 2023 00:54:56 +0800 Subject: [PATCH 27/30] readme --- README-ZH_CN.md | 12 ++++++------ README.md | 12 ++++++------ UPDATE_LOG.md | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/README-ZH_CN.md b/README-ZH_CN.md index 42b149a..92d31a6 100644 --- a/README-ZH_CN.md +++ b/README-ZH_CN.md @@ -24,7 +24,7 @@ ### Windows -- [ChatGPT_0.9.2_x64_en-US.msi](https://github.com/lencx/ChatGPT/releases/download/v0.9.2/ChatGPT_0.9.2_x64_en-US.msi): +- [ChatGPT_0.10.0_x64_en-US.msi](https://github.com/lencx/ChatGPT/releases/download/v0.10.0/ChatGPT_0.10.0_x64_en-US.msi): - 使用 [winget](https://winstall.app/apps/lencx.ChatGPT): ```bash @@ -35,12 +35,12 @@ winget install --id=lencx.ChatGPT -e --version 0.9.0 ``` -**注意:如果安装路径和应用名称相同,会导致冲突 ([#142](https://github.com/lencx/ChatGPT/issues/142#issuecomment-0.9.2))** +**注意:如果安装路径和应用名称相同,会导致冲突 ([#142](https://github.com/lencx/ChatGPT/issues/142#issuecomment-0.10.0))** ### Mac -- [ChatGPT_0.9.2_x64.dmg](https://github.com/lencx/ChatGPT/releases/download/v0.9.2/ChatGPT_0.9.2_x64.dmg) -- [ChatGPT.app.tar.gz](https://github.com/lencx/ChatGPT/releases/download/v0.9.2/ChatGPT.app.tar.gz) +- [ChatGPT_0.10.0_x64.dmg](https://github.com/lencx/ChatGPT/releases/download/v0.10.0/ChatGPT_0.10.0_x64.dmg) +- [ChatGPT.app.tar.gz](https://github.com/lencx/ChatGPT/releases/download/v0.10.0/ChatGPT.app.tar.gz) - Homebrew \ _[Homebrew 快捷安装](https://brew.sh) ([Cask](https://docs.brew.sh/Cask-Cookbook)):_ ```sh @@ -56,8 +56,8 @@ ### Linux -- [chat-gpt_0.9.2_amd64.deb](https://github.com/lencx/ChatGPT/releases/download/v0.9.2/chat-gpt_0.9.2_amd64.deb) -- [chat-gpt_0.9.2_amd64.AppImage](https://github.com/lencx/ChatGPT/releases/download/v0.9.2/chat-gpt_0.9.2_amd64.AppImage): **工作可靠,`.deb` 运行失败时可以尝试它** +- [chat-gpt_0.10.0_amd64.deb](https://github.com/lencx/ChatGPT/releases/download/v0.10.0/chat-gpt_0.10.0_amd64.deb) +- [chat-gpt_0.10.0_amd64.AppImage](https://github.com/lencx/ChatGPT/releases/download/v0.10.0/chat-gpt_0.10.0_amd64.AppImage): **工作可靠,`.deb` 运行失败时可以尝试它** - 使用 [AUR](https://aur.archlinux.org/packages/chatgpt-desktop-bin): ```bash yay -S chatgpt-desktop-bin diff --git a/README.md b/README.md index 2f008f6..58af401 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ ### Windows -- [ChatGPT_0.9.2_x64_en-US.msi](https://github.com/lencx/ChatGPT/releases/download/v0.9.2/ChatGPT_0.9.2_x64_en-US.msi): Direct download installer +- [ChatGPT_0.10.0_x64_en-US.msi](https://github.com/lencx/ChatGPT/releases/download/v0.10.0/ChatGPT_0.10.0_x64_en-US.msi): Direct download installer - Use [winget](https://winstall.app/apps/lencx.ChatGPT): ```bash @@ -37,12 +37,12 @@ winget install --id=lencx.ChatGPT -e --version 0.9.0 ``` -**Note: If the installation path and application name are the same, it will lead to conflict ([#142](https://github.com/lencx/ChatGPT/issues/142#issuecomment-0.9.2))** +**Note: If the installation path and application name are the same, it will lead to conflict ([#142](https://github.com/lencx/ChatGPT/issues/142#issuecomment-0.10.0))** ### Mac -- [ChatGPT_0.9.2_x64.dmg](https://github.com/lencx/ChatGPT/releases/download/v0.9.2/ChatGPT_0.9.2_x64.dmg): Direct download installer -- [ChatGPT.app.tar.gz](https://github.com/lencx/ChatGPT/releases/download/v0.9.2/ChatGPT.app.tar.gz): Download the `.app` installer +- [ChatGPT_0.10.0_x64.dmg](https://github.com/lencx/ChatGPT/releases/download/v0.10.0/ChatGPT_0.10.0_x64.dmg): Direct download installer +- [ChatGPT.app.tar.gz](https://github.com/lencx/ChatGPT/releases/download/v0.10.0/ChatGPT.app.tar.gz): Download the `.app` installer - Homebrew \ Or you can install with _[Homebrew](https://brew.sh) ([Cask](https://docs.brew.sh/Cask-Cookbook)):_ ```sh @@ -58,8 +58,8 @@ ### Linux -- [chat-gpt_0.9.2_amd64.deb](https://github.com/lencx/ChatGPT/releases/download/v0.9.2/chat-gpt_0.9.2_amd64.deb): Download `.deb` installer, advantage small size, disadvantage poor compatibility -- [chat-gpt_0.9.2_amd64.AppImage](https://github.com/lencx/ChatGPT/releases/download/v0.9.2/chat-gpt_0.9.2_amd64.AppImage): Works reliably, you can try it if `.deb` fails to run +- [chat-gpt_0.10.0_amd64.deb](https://github.com/lencx/ChatGPT/releases/download/v0.10.0/chat-gpt_0.10.0_amd64.deb): Download `.deb` installer, advantage small size, disadvantage poor compatibility +- [chat-gpt_0.10.0_amd64.AppImage](https://github.com/lencx/ChatGPT/releases/download/v0.10.0/chat-gpt_0.10.0_amd64.AppImage): Works reliably, you can try it if `.deb` fails to run - Available on [AUR](https://aur.archlinux.org/packages/chatgpt-desktop-bin) with the package name `chatgpt-desktop-bin`, and you can use your favourite AUR package manager to install it. diff --git a/UPDATE_LOG.md b/UPDATE_LOG.md index ce685ae..e4322b6 100644 --- a/UPDATE_LOG.md +++ b/UPDATE_LOG.md @@ -4,7 +4,7 @@ Fix: -- After exporting the file, open an empty file explorer (https://github.com/lencx/ChatGPT/issues/242) +- After exporting a file in Windows, open an empty file explorer (https://github.com/lencx/ChatGPT/issues/242) Feat: From 9ba3357a584b8b8e3a57e5b28962276a53871dab Mon Sep 17 00:00:00 2001 From: lencx Date: Wed, 25 Jan 2023 01:52:37 +0800 Subject: [PATCH 28/30] chore: tray --- .github/workflows/release.yml | 20 +++++++------- src-tauri/Cargo.toml | 2 +- src-tauri/src/app/setup.rs | 18 ++++++------- src-tauri/src/app/window.rs | 39 +++++++++++++-------------- src/components/SwitchOrigin/index.tsx | 22 ++++++++------- src/view/awesome/Form.tsx | 2 +- 6 files changed, 50 insertions(+), 53 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3913b95..c6f2d4e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -90,13 +90,13 @@ jobs: publish_dir: ./updater force_orphan: true - # publish-winget: - # # Action can only be run on windows - # runs-on: windows-latest - # needs: [create-release, build-tauri] - # steps: - # - uses: vedantmgoyal2009/winget-releaser@v1 - # with: - # identifier: lencx.ChatGPT - # token: ${{ secrets.WINGET_TOKEN }} - # version: ${{ env.version }} + publish-winget: + # Action can only be run on windows + runs-on: windows-latest + needs: [create-release, build-tauri] + steps: + - uses: vedantmgoyal2009/winget-releaser@v1 + with: + identifier: lencx.ChatGPT + token: ${{ secrets.WINGET_TOKEN }} + version: ${{ env.version }} diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index cbf996f..f0f4147 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -26,7 +26,7 @@ wry = "0.24.1" dark-light = "1.0.0" serde = { version = "1.0", features = ["derive"] } tokio = { version = "1.23.0", features = ["macros"] } -tauri = { version = "1.2.3", features = ["api-all", "devtools", "global-shortcut", "system-tray", "updater"] } +tauri = { version = "1.2.4", features = ["api-all", "devtools", "global-shortcut", "system-tray", "updater"] } tauri-plugin-positioner = { version = "1.0.4", features = ["system-tray"] } tauri-plugin-log = { git = "https://github.com/lencx/tauri-plugins-workspace", branch = "dev", features = ["colored"] } tauri-plugin-autostart = { git = "https://github.com/lencx/tauri-plugins-workspace", branch = "dev" } diff --git a/src-tauri/src/app/setup.rs b/src-tauri/src/app/setup.rs index 3aedf0f..7cb82b9 100644 --- a/src-tauri/src/app/setup.rs +++ b/src-tauri/src/app/setup.rs @@ -59,7 +59,13 @@ pub fn init(app: &mut App) -> std::result::Result<(), Box .title("ChatGPT") .resizable(true) .fullscreen(false) - .inner_size(800.0, 600.0); + .inner_size(800.0, 600.0) + .theme(theme) + .always_on_top(chat_conf.stay_on_top) + .title_bar_style(ChatConfJson::titlebar()) + .initialization_script(&utils::user_script()) + .initialization_script(include_str!("../scripts/core.js")) + .user_agent(&chat_conf.ua_window); if cfg!(target_os = "macos") { main_win = main_win.hidden_title(true); @@ -79,15 +85,7 @@ pub fn init(app: &mut App) -> std::result::Result<(), Box .initialization_script(include_str!("../scripts/cmd.js")) } - main_win - .theme(theme) - .always_on_top(chat_conf.stay_on_top) - .title_bar_style(ChatConfJson::titlebar()) - .initialization_script(&utils::user_script()) - .initialization_script(include_str!("../scripts/core.js")) - .user_agent(&chat_conf.ua_window) - .build() - .unwrap(); + main_win.build().unwrap(); }); } diff --git a/src-tauri/src/app/window.rs b/src-tauri/src/app/window.rs index e23daa8..cd7cf87 100644 --- a/src-tauri/src/app/window.rs +++ b/src-tauri/src/app/window.rs @@ -9,22 +9,24 @@ pub fn tray_window(handle: &tauri::AppHandle) { let app = handle.clone(); tauri::async_runtime::spawn(async move { - let mut tray_win = WindowBuilder::new( - &app, - "tray", - WindowUrl::App(chat_conf.tray_origin.clone().into()), - ) - .title("ChatGPT") - .resizable(false) - .fullscreen(false) - .inner_size(360.0, 540.0) - .decorations(false) - .always_on_top(true) - .theme(theme) - .initialization_script(&utils::user_script()) - .initialization_script(include_str!("../scripts/core.js")); + let link = if chat_conf.tray_dashboard { + "index.html" + } else { + &chat_conf.tray_origin + }; + let mut tray_win = WindowBuilder::new(&app, "tray", WindowUrl::App(link.into())) + .title("ChatGPT") + .resizable(false) + .fullscreen(false) + .inner_size(360.0, 540.0) + .decorations(false) + .always_on_top(true) + .theme(theme) + .initialization_script(&utils::user_script()) + .initialization_script(include_str!("../scripts/core.js")) + .user_agent(&chat_conf.ua_tray); - if chat_conf.tray_origin == "https://chat.openai.com" { + if chat_conf.tray_origin == "https://chat.openai.com" && !chat_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")) @@ -32,12 +34,7 @@ pub fn tray_window(handle: &tauri::AppHandle) { .initialization_script(include_str!("../scripts/popup.core.js")) } - tray_win - .user_agent(&chat_conf.ua_tray) - .build() - .unwrap() - .hide() - .unwrap(); + tray_win.build().unwrap().hide().unwrap(); }); } diff --git a/src/components/SwitchOrigin/index.tsx b/src/components/SwitchOrigin/index.tsx index d51c510..e9fd427 100644 --- a/src/components/SwitchOrigin/index.tsx +++ b/src/components/SwitchOrigin/index.tsx @@ -74,16 +74,18 @@ const SwitchOrigin: FC = ({ name }) => { name={originName} > diff --git a/src/view/awesome/Form.tsx b/src/view/awesome/Form.tsx index baee30b..26b972e 100644 --- a/src/view/awesome/Form.tsx +++ b/src/view/awesome/Form.tsx @@ -39,7 +39,7 @@ const AwesomeForm: ForwardRefRenderFunction = ({ re From c55ac1df3bec205d1732d43f062b1b91e4d50875 Mon Sep 17 00:00:00 2001 From: lencx Date: Wed, 25 Jan 2023 01:53:45 +0800 Subject: [PATCH 29/30] v0.10.0 --- src-tauri/tauri.conf.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index ccd136c..970c87f 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -7,7 +7,7 @@ }, "package": { "productName": "ChatGPT", - "version": "0.9.2" + "version": "0.10.0" }, "tauri": { "allowlist": { From 817cd6f87ca7dd202a1d6482dde2e0181c52dd9f Mon Sep 17 00:00:00 2001 From: lencx Date: Wed, 25 Jan 2023 02:09:23 +0800 Subject: [PATCH 30/30] fix: build error --- src-tauri/src/app/setup.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src-tauri/src/app/setup.rs b/src-tauri/src/app/setup.rs index 7cb82b9..f596ec3 100644 --- a/src-tauri/src/app/setup.rs +++ b/src-tauri/src/app/setup.rs @@ -62,13 +62,14 @@ pub fn init(app: &mut App) -> std::result::Result<(), Box .inner_size(800.0, 600.0) .theme(theme) .always_on_top(chat_conf.stay_on_top) - .title_bar_style(ChatConfJson::titlebar()) .initialization_script(&utils::user_script()) .initialization_script(include_str!("../scripts/core.js")) .user_agent(&chat_conf.ua_window); if cfg!(target_os = "macos") { - main_win = main_win.hidden_title(true); + main_win = main_win + .title_bar_style(ChatConfJson::titlebar()) + .hidden_title(true); } if url == "https://chat.openai.com" && !chat_conf.main_dashboard {