Compare commits

..

5 Commits

Author SHA1 Message Date
lencx
1607261e8f v0.1.6 2022-12-10 03:47:50 +08:00
lencx
508875a944 chore: export 2022-12-10 03:42:41 +08:00
lencx
f585f58987 readme 2022-12-10 03:29:50 +08:00
lencx
0d897e0176 feat: export history 2022-12-10 03:23:29 +08:00
lencx
dc33c6eb4f feat: import 2022-12-09 13:44:18 +08:00
23 changed files with 102 additions and 684 deletions

View File

@@ -1,20 +0,0 @@
# Awesome ChatGPT
- [Awesome ChatGPT Prompts](https://github.com/f/awesome-chatgpt-prompts) - This repo includes ChatGPT promt curation to use ChatGPT better.
- [Awesome ChatGPT](https://github.com/humanloop/awesome-chatgpt) - Curated list of awesome tools, demos, docs for ChatGPT and GPT-3
## Extension
`Browser`
- [ChatGPT Export and Share](https://github.com/liady/ChatGPT-pdf) - A Chrome extension for downloading your ChatGPT history to PNG, PDF or creating a sharable link
- [ChatGPT for Google](https://github.com/wong2/chat-gpt-google-extension) - A browser extension to display ChatGPT response alongside Google Search results
- [ChatGPT Extension](https://github.com/kazuki-sf/ChatGPT_Extension) - ChatGPT Extension is a really simple Chrome Extension (manifest v3) that you can access OpenAI's ChatGPT from anywhere on the web.
`VSCode`
- [ChatGPT Extension for VSCode](https://github.com/mpociot/chatgpt-vscode) - A VSCode extension that allows you to use ChatGPT
`Bot`
- [ChatGPT Telegram Bot](https://github.com/altryne/chatGPT-telegram-bot) - This is a very early attempt at having chatGPT work within a telegram bot

View File

@@ -5,45 +5,19 @@
> ChatGPT Desktop Application
## Downloads
[![ChatGPT downloads](https://img.shields.io/github/downloads/lencx/ChatGPT/total.svg?style=flat-square)](https://github.com/lencx/ChatGPT/releases)
[![lencx](https://img.shields.io/twitter/follow/lencx_.svg?style=social)](https://twitter.com/lencx_)
[Awesome ChatGPT](./AWESOME.md)
## 📦 Downloads
[📝 Update Log](./UPDATE_LOG.md)
<!-- download start -->
**Latest:**
- `Mac`: [ChatGPT_0.2.0_x64.dmg](https://github.com/lencx/ChatGPT/releases/download/v0.2.0/ChatGPT_0.2.0_x64.dmg)
- `Linux`: [chat-gpt_0.2.0_amd64.deb](https://github.com/lencx/ChatGPT/releases/download/v0.2.0/chat-gpt_0.2.0_amd64.deb)
- `Windows`: [ChatGPT_0.2.0_x64_en-US.msi](https://github.com/lencx/ChatGPT/releases/download/v0.2.0/ChatGPT_0.2.0_x64_en-US.msi)
- `Mac`: [ChatGPT_0.1.6_x64.dmg](https://github.com/lencx/ChatGPT/releases/download/v0.1.5/ChatGPT_0.1.6_x64.dmg)
- `Linux`: [chat-gpt_0.1.6_amd64.deb](https://github.com/lencx/ChatGPT/releases/download/v0.1.5/chat-gpt_0.1.6_amd64.deb)
- `Windows`: [ChatGPT_0.1.6_x64_en-US.msi](https://github.com/lencx/ChatGPT/releases/download/v0.1.5/ChatGPT_0.1.6_x64_en-US.msi)
[Other version...](https://github.com/lencx/ChatGPT/releases)
<!-- download end -->
### Install
Easily install with _[Homebrew](https://brew.sh) ([Cask](https://docs.brew.sh/Cask-Cookbook)):_
~~~ sh
brew tap lencx/chatgpt https://github.com/lencx/ChatGPT.git
brew install --cask chatgpt --no-quarantine
~~~
Also, if you keep a _[Brewfile](https://github.com/Homebrew/homebrew-bundle#usage)_, you can add something like this:
~~~ rb
repo = "lencx/chatgpt"
tap repo, "https://github.com/#{repo}.git"
cask "popcorn-time", args: { "no-quarantine": true }
~~~
## ✨ Features
## Features
- multi-platform: `macOS` `Linux` `Windows`
- export ChatGPT history (PNG, PDF and Share Link)
@@ -51,48 +25,15 @@ cask "popcorn-time", args: { "no-quarantine": true }
- inject script
- auto updater
- app menu
- tray window
- system tray
- shortcut
### Menu
## Preview
- **Preferences**
- `Theme` - `Light`, `Dark` (Only macOS and Windows are supported).
- `Always On Top`: Window is always on top of other windows.
- `Titlebar`: Only supports macOS.
- `User Agent` ([#17](https://github.com/lencx/ChatGPT/issues/17)): Customize `user agent` to prevent security detection interception. Default is empty string.
- `Inject Script`: User scripts that can modify web pages.
- `Switch Origin` ([#14](https://github.com/lencx/ChatGPT/issues/14)): Modify website address, the default is `https://chat.openai.com`. Please ensure that the mirror address is consistent with the UI of the original URL, otherwise the export function will fail.
- `Clear Config`: Clear all chatgpt configuration files (`path: ~/.chatgpt/*`), dangerous operation, please backup data.
- `Restart ChatGPT`: After editing the injection script file, you can restart the application through this menu item to make the script take effect.
- `Awesome ChatGPT`: Related resources recommended.
- **Edit** - `Undo`, `Redo`, `Cut`, `Copy`, `SelectAll`, ...
- **View** - `Go Back`, `Go Forward`, `Scroll to Top of Screen`, `Scroll to Bottom of Screen`, `Refresh the Screen`, ...
- **Help**
- `Update Log`: ChatGPT app changelog.
- `Report Bug`: Defects and Suggestions Feedback.
- `Toggle Developer Tools`: Developer tools for debugging web pages.
<img width="360" src="./assets/install.png" alt="install"> <img width="360" src="./assets/chat.png" alt="chat">
<img width="360" src="./assets/export.png" alt="export"> <img width="360" src="./assets/auto-update.png" alt="auto update">
## TODO
- web access capability ([#20](https://github.com/lencx/ChatGPT/issues/20))
- ...
## 👀 Preview
<img width="320" src="./assets/install.png" alt="install"> <img width="320" src="./assets/chat.png" alt="chat">
<img width="320" src="./assets/export.png" alt="export"> <img width="320" src="./assets/tray.png" alt="tray">
<img width="320" src="./assets/chat-ua.png" alt="user agent"> <img width="320" src="./assets/auto-update.png" alt="auto update">
---
<a href="https://www.buymeacoffee.com/lencx" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-blue.png" alt="Buy Me A Coffee" style="height: 60px !important;width: 217px !important;" ></a>
## ❓FAQ
### Can't open ChatGPT
If the application cannot be opened after the upgrade, please try to clear the configuration file, which is in the `~/.chatgpt/*` directory.
## FAQ
### Is it safe?
@@ -131,12 +72,6 @@ yarn dev
yarn build
```
## ❤️ Thanks
- The share buttons code is copied directly from [@liady](https://github.com/liady) extension with minor modifications.
## Related
- [Tauri](https://tauri.app) - Build an optimized, secure, and frontend-independent application for multi-platform deployment.
- [ChatGPT](https://openai.com/blog/chatgpt) - ChatGPT: Optimizing Language Models for Dialogue.
- [ChatGPT Export and Share](https://github.com/liady/ChatGPT-pdf) - A Chrome extension for downloading your ChatGPT history to PNG, PDF or creating a sharable link.
- [ChatGPT Export and Share](https://github.com/liady/ChatGPT-pdf) - A Chrome extension for downloading your ChatGPT history to PNG, PDF or creating a sharable link

View File

@@ -1,25 +1,5 @@
# UPDATE LOG
## v0.2.1
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
## v0.1.7
feat: tray window
## v0.1.6
feat:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 391 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 543 KiB

View File

@@ -1,22 +0,0 @@
cask "chatgpt" do
version "0.1.7"
sha256 "1320b30a67e2506f9b45ffd2a48243d6141171c231dd698994ae5156a637eb3f"
url "https://github.com/lencx/ChatGPT/releases/download/v#{version}/ChatGPT_#{version}_x64.dmg"
name "ChatGPT"
desc "Desktop wrapper for OpenAI ChatGPT"
homepage "https://github.com/lencx/ChatGPT#readme"
app "ChatGPT.app"
uninstall quit: "com.lencx.chatgpt"
zap trash: [
"~/.chatgpt",
"~/Library/Caches/com.lencx.chatgpt",
"~/Library/HTTPStorages/com.lencx.chatgpt.binarycookies",
"~/Library/Preferences/com.lencx.chatgpt.plist",
"~/Library/Saved Application State/com.lencx.chatgpt.savedState",
"~/Library/WebKit/com.lencx.chatgpt",
]
end

View File

@@ -6,21 +6,12 @@
"build": "yarn tauri build",
"updater": "tr updater",
"release": "tr release --git",
"download": "node ./scripts/download.js",
"tr": "tr",
"tauri": "tauri"
},
"license": "MIT",
"author": "lencx <cxin1314@gmail.com>",
"keywords": [
"chatgpt",
"app",
"desktop",
"tauri",
"macos",
"linux",
"windows"
],
"keywords": ["chatgpt", "app", "desktop", "tauri", "macos", "linux", "windows"],
"homepage": "https://github.com/lencx/ChatGPT",
"bugs": "https://github.com/lencx/ChatGPT/issues",
"repository": {

26
scripts/download.js vendored
View File

@@ -1,26 +0,0 @@
const fs = require('fs');
const argv = process.argv.slice(2);
async function init() {
const content = fs.readFileSync('README.md', 'utf8').split('\n');
const startRe = /<!-- download start -->/;
const endRe = /<!-- download end -->/;
let flag = false;
for (let i = 0; i < content.length; i++) {
if (startRe.test(content[i])) {
flag = true;
}
if (flag) {
content[i] = content[i].replace(/(\d+).(\d+).(\d+)/g, argv[0]);
}
if (endRe.test(content[i])) {
break;
}
}
fs.writeFileSync('README.md', content.join('\n'), 'utf8');
}
init().catch(console.error);

View File

@@ -14,11 +14,10 @@ rust-version = "1.57"
tauri-build = {version = "1.2.1", features = [] }
[dependencies]
anyhow = "1.0.66"
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
tauri = { version = "1.2.2", features = ["api-all", "devtools", "system-tray", "updater"] }
tauri-plugin-positioner = { version = "1.0.4", features = ["system-tray"] }
tauri = { version = "1.2.1", features = ["api-all", "devtools", "system-tray", "updater"] }
anyhow = "1.0.66"
[features]
# by default Tauri runs in production mode

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

View File

@@ -1,4 +1,4 @@
use crate::{conf::ChatConfJson, utils};
use crate::utils;
use std::fs;
use tauri::{api, command, AppHandle, Manager};
@@ -28,35 +28,3 @@ pub fn download(_app: AppHandle, name: String, blob: Vec<u8>) {
pub fn open_link(app: AppHandle, url: String) {
api::shell::open(&app.shell_scope(), url, None).unwrap();
}
#[command]
pub fn get_chat_conf() -> ChatConfJson {
ChatConfJson::get_chat_conf()
}
#[command]
pub fn form_confirm(app: AppHandle, data: serde_json::Value) {
ChatConfJson::amend(&serde_json::json!(data)).unwrap();
tauri::api::process::restart(&app.env());
}
#[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();
}
},
);
}
#[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);
}

View File

@@ -1,23 +1,17 @@
use crate::{
app::window,
conf::{self, ChatConfJson},
utils,
};
use crate::{conf, utils};
use tauri::{
utils::assets::EmbeddedAssets, AboutMetadata, AppHandle, Context, CustomMenuItem, Manager,
Menu, MenuItem, Submenu, SystemTray, SystemTrayEvent, SystemTrayMenu, WindowMenuEvent,
};
use tauri_plugin_positioner::{on_tray_event, Position, WindowExt};
// --- Menu
pub fn init(context: &Context<EmbeddedAssets>) -> Menu {
let chat_conf = ChatConfJson::get_chat_conf();
pub fn init(chat_conf: &conf::ChatConfJson, context: &Context<EmbeddedAssets>) -> Menu {
let name = &context.package_info().name;
let app_menu = Submenu::new(
name,
Menu::new()
.add_native_item(MenuItem::About(name.into(), AboutMetadata::default()))
.add_native_item(MenuItem::Services)
.add_native_item(MenuItem::Separator)
.add_native_item(MenuItem::Separator)
.add_native_item(MenuItem::Hide)
.add_native_item(MenuItem::HideOthers)
@@ -28,68 +22,24 @@ pub fn init(context: &Context<EmbeddedAssets>) -> Menu {
let always_on_top = CustomMenuItem::new("always_on_top".to_string(), "Always On Top")
.accelerator("CmdOrCtrl+T");
let titlebar =
CustomMenuItem::new("titlebar".to_string(), "Titlebar").accelerator("CmdOrCtrl+B");
let theme_light = CustomMenuItem::new("theme_light".to_string(), "Light");
let theme_dark = CustomMenuItem::new("theme_dark".to_string(), "Dark");
let is_dark = chat_conf.theme == "Dark";
let always_on_top_menu = if chat_conf.always_on_top {
always_on_top.selected()
} else {
always_on_top
};
let titlebar_menu = if chat_conf.titlebar {
titlebar.selected()
} else {
titlebar
};
let preferences_menu = Submenu::new(
"Preferences",
Menu::with_items([
Submenu::new(
"Theme",
Menu::new()
.add_item(if is_dark {
theme_light
} else {
theme_light.selected()
})
.add_item(if is_dark {
theme_dark.selected()
} else {
theme_dark
}),
Menu::new()
.add_item(
CustomMenuItem::new("inject_script".to_string(), "Inject Script")
.accelerator("CmdOrCtrl+J"),
)
.into(),
always_on_top_menu.into(),
#[cfg(target_os = "macos")]
titlebar_menu.into(),
MenuItem::Separator.into(),
// fix: Checking if the site connection is secure
// @link: https://github.com/lencx/ChatGPT/issues/17
CustomMenuItem::new("user_agent".to_string(), "User Agent")
.accelerator("CmdOrCtrl+U")
.into(),
CustomMenuItem::new("switch_origin".to_string(), "Switch Origin")
.accelerator("CmdOrCtrl+O")
.into(),
CustomMenuItem::new("inject_script".to_string(), "Inject Script")
.accelerator("CmdOrCtrl+J")
.into(),
MenuItem::Separator.into(),
CustomMenuItem::new("clear_conf".to_string(), "Clear Config")
.accelerator("CmdOrCtrl+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+Z")
.into(),
]),
.add_item(if chat_conf.always_on_top {
always_on_top.selected()
} else {
always_on_top
})
.add_native_item(MenuItem::Separator)
.add_item(
CustomMenuItem::new("awesome".to_string(), "Awesome ChatGPT")
.accelerator("CmdOrCtrl+Z"),
),
);
let edit_menu = Submenu::new(
@@ -122,7 +72,6 @@ pub fn init(context: &Context<EmbeddedAssets>) -> Menu {
CustomMenuItem::new("scroll_bottom".to_string(), "Scroll to Bottom of Screen")
.accelerator("CmdOrCtrl+Down"),
)
.add_native_item(MenuItem::Zoom)
.add_native_item(MenuItem::Separator)
.add_item(
CustomMenuItem::new("reload".to_string(), "Refresh the Screen")
@@ -133,7 +82,6 @@ pub fn init(context: &Context<EmbeddedAssets>) -> Menu {
let help_menu = Submenu::new(
"Help",
Menu::new()
.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")
@@ -162,26 +110,8 @@ pub fn menu_handler(event: WindowMenuEvent<tauri::Wry>) {
match menu_id {
// Preferences
"restart" => tauri::api::process::restart(&app.env()),
"inject_script" => open(&app, script_path),
"clear_conf" => utils::clear_conf(&app),
"switch_origin" => window::origin_window(&app),
"user_agent" => window::ua_window(&app),
"awesome" => open(&app, conf::AWESOME_URL.to_string()),
"titlebar" => {
let chat_conf = conf::ChatConfJson::get_chat_conf();
ChatConfJson::amend(&serde_json::json!({ "titlebar": !chat_conf.titlebar })).unwrap();
tauri::api::process::restart(&app.env());
}
"theme_light" | "theme_dark" => {
let theme = if menu_id == "theme_dark" {
"Dark"
} else {
"Light"
};
ChatConfJson::amend(&serde_json::json!({ "theme": theme })).unwrap();
tauri::api::process::restart(&app.env());
}
"always_on_top" => {
let mut always_on_top = state.always_on_top.lock().unwrap();
*always_on_top = !*always_on_top;
@@ -190,7 +120,7 @@ pub fn menu_handler(event: WindowMenuEvent<tauri::Wry>) {
.set_selected(*always_on_top)
.unwrap();
win.set_always_on_top(*always_on_top).unwrap();
ChatConfJson::amend(&serde_json::json!({ "always_on_top": *always_on_top })).unwrap();
conf::ChatConfJson::update_chat_conf(*always_on_top);
}
// View
"reload" => win.eval("window.location.reload()").unwrap(),
@@ -214,7 +144,6 @@ pub fn menu_handler(event: WindowMenuEvent<tauri::Wry>) {
)
.unwrap(),
// Help
"update_log" => open(&app, conf::UPDATE_LOG_URL.to_string()),
"report_bug" => open(&app, conf::ISSUES_URL.to_string()),
"dev_tools" => {
win.open_devtools();
@@ -230,19 +159,16 @@ pub fn tray_menu() -> SystemTray {
}
// --- SystemTray Event
pub fn tray_handler(handle: &AppHandle, event: SystemTrayEvent) {
let core_win = handle.get_window("core").unwrap();
on_tray_event(handle, &event);
pub fn tray_handler(app: &AppHandle, event: SystemTrayEvent) {
let win = app.get_window("core").unwrap();
if let SystemTrayEvent::LeftClick { .. } = event {
core_win.minimize().unwrap();
let mini_win = handle.get_window("mini").unwrap();
mini_win.move_window(Position::TrayCenter).unwrap();
if mini_win.is_visible().unwrap() {
mini_win.hide().unwrap();
// TODO: tray window
if win.is_visible().unwrap() {
win.hide().unwrap();
} else {
mini_win.show().unwrap();
win.show().unwrap();
win.set_focus().unwrap();
}
}
}

View File

@@ -1,4 +1,3 @@
pub mod cmd;
pub mod menu;
pub mod setup;
pub mod window;

View File

@@ -1,11 +1,15 @@
use crate::{app::window, conf::ChatConfJson, utils};
use tauri::{utils::config::WindowUrl, window::WindowBuilder, App, Manager};
use crate::{conf, utils};
use tauri::{utils::config::WindowUrl, window::WindowBuilder, App};
pub fn init(app: &mut App) -> std::result::Result<(), Box<dyn std::error::Error>> {
let chat_conf = ChatConfJson::get_chat_conf();
let url = chat_conf.origin.to_string();
let theme = ChatConfJson::theme();
window::mini_window(&app.app_handle());
#[cfg(target_os = "macos")]
use tauri::TitleBarStyle;
pub fn init(
app: &mut App,
chat_conf: conf::ChatConfJson,
) -> std::result::Result<(), Box<dyn std::error::Error>> {
let conf = utils::get_tauri_conf().unwrap();
let url = conf.build.dev_path.to_string();
#[cfg(target_os = "macos")]
WindowBuilder::new(app, "core", WindowUrl::App(url.into()))
@@ -13,15 +17,14 @@ pub fn init(app: &mut App) -> std::result::Result<(), Box<dyn std::error::Error>
.fullscreen(false)
.inner_size(800.0, 600.0)
.hidden_title(true)
.theme(theme)
.title_bar_style(TitleBarStyle::Overlay)
.always_on_top(chat_conf.always_on_top)
.title_bar_style(ChatConfJson::titlebar())
.initialization_script(&utils::user_script())
.initialization_script(include_str!("../assets/html2canvas.js"))
.initialization_script(include_str!("../assets/jspdf.js"))
.initialization_script(include_str!("../assets/core.js"))
.initialization_script(include_str!("../assets/export.js"))
.user_agent(&chat_conf.ua_pc)
.user_agent(conf::USER_AGENT)
.build()?;
#[cfg(not(target_os = "macos"))]
@@ -30,14 +33,13 @@ pub fn init(app: &mut App) -> std::result::Result<(), Box<dyn std::error::Error>
.resizable(true)
.fullscreen(false)
.inner_size(800.0, 600.0)
.theme(theme)
.always_on_top(chat_conf.always_on_top)
.initialization_script(&utils::user_script())
.initialization_script(include_str!("../assets/html2canvas.js"))
.initialization_script(include_str!("../assets/jspdf.js"))
.initialization_script(include_str!("../assets/core.js"))
.initialization_script(include_str!("../assets/export.js"))
.user_agent(&chat_conf.ua_pc)
.user_agent(conf::USER_AGENT)
.build()?;
Ok(())

View File

@@ -1,53 +0,0 @@
use crate::{conf, utils};
use tauri::{utils::config::WindowUrl, window::WindowBuilder};
pub fn mini_window(handle: &tauri::AppHandle) {
let chat_conf = conf::ChatConfJson::get_chat_conf();
let theme = conf::ChatConfJson::theme();
WindowBuilder::new(handle, "mini", WindowUrl::App(chat_conf.origin.into()))
.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!("../assets/html2canvas.js"))
.initialization_script(include_str!("../assets/jspdf.js"))
.initialization_script(include_str!("../assets/core.js"))
.initialization_script(include_str!("../assets/export.js"))
.user_agent(&chat_conf.ua_phone)
.build()
.unwrap()
.hide()
.unwrap();
}
pub fn origin_window(handle: &tauri::AppHandle) {
let chat_conf = conf::ChatConfJson::get_chat_conf();
WindowBuilder::new(handle, "origin", WindowUrl::App(chat_conf.origin.into()))
.resizable(false)
.fullscreen(false)
.inner_size(400.0, 300.0)
.always_on_top(true)
.decorations(false)
.initialization_script(include_str!("../assets/core.js"))
.initialization_script(include_str!("../assets/origin.js"))
.build()
.unwrap();
}
pub fn ua_window(handle: &tauri::AppHandle) {
let chat_conf = conf::ChatConfJson::get_chat_conf();
WindowBuilder::new(handle, "ua", WindowUrl::App(chat_conf.origin.into()))
.resizable(false)
.fullscreen(false)
.inner_size(540.0, 480.0)
.always_on_top(true)
.decorations(false)
.initialization_script(include_str!("../assets/core.js"))
.initialization_script(include_str!("../assets/ua.js"))
.build()
.unwrap();
}

View File

@@ -40,12 +40,6 @@ window.invoke = invoke;
window.transformCallback = transformCallback;
async function init() {
if (__TAURI_METADATA__.__currentWindow.label === 'mini') {
document.getElementsByTagName('html')[0].style['font-size'] = '70%';
}
if (__TAURI_METADATA__.__currentWindow.label !== 'core') return;
async function platform() {
return invoke('platform', {
__tauriModule: 'Os',
@@ -54,8 +48,7 @@ async function init() {
}
const _platform = await platform();
const chatConf = await invoke('get_chat_conf') || {};
if (/darwin/.test(_platform) && !chatConf.titlebar) {
if (/darwin/.test(_platform)) {
const topStyleDom = document.createElement("style");
topStyleDom.innerHTML = `#chatgpt-app-window-top{position:fixed;top:0;z-index:999999999;width:100%;height:24px;background:transparent;cursor:grab;cursor:-webkit-grab;user-select:none;-webkit-user-select:none;}#chatgpt-app-window-top:active {cursor:grabbing;cursor:-webkit-grabbing;}`;
document.head.appendChild(topStyleDom);
@@ -71,7 +64,7 @@ async function init() {
document.addEventListener("click", (e) => {
const origin = e.target.closest("a");
if (origin && origin.href && origin.target !== '_self') {
invoke('open_link', { url: origin.href });
origin.target = "_self";
}
});

View File

@@ -2,7 +2,6 @@
// @ref: https://github.com/liady/ChatGPT-pdf/blob/main/src/content_script.js
async function init() {
const chatConf = await invoke('get_chat_conf') || {};
if (window.buttonsInterval) {
clearInterval(window.buttonsInterval);
}
@@ -17,7 +16,7 @@ async function init() {
});
if (hasTryAgainButton && buttons.length === 1) {
const TryAgainButton = actionsArea.querySelector("button");
addActionsButtons(actionsArea, TryAgainButton, chatConf);
addActionsButtons(actionsArea, TryAgainButton);
} else if (!hasTryAgainButton) {
removeButtons();
}
@@ -29,7 +28,7 @@ const Format = {
PDF: "pdf",
};
function addActionsButtons(actionsArea, TryAgainButton, chatConf) {
function addActionsButtons(actionsArea, TryAgainButton) {
const downloadButton = TryAgainButton.cloneNode(true);
downloadButton.id = "download-png-button";
downloadButton.innerText = "Generate PNG";
@@ -37,7 +36,6 @@ function addActionsButtons(actionsArea, TryAgainButton, chatConf) {
downloadThread();
};
actionsArea.appendChild(downloadButton);
const downloadPdfButton = TryAgainButton.cloneNode(true);
downloadPdfButton.id = "download-pdf-button";
downloadPdfButton.innerText = "Download PDF";
@@ -45,16 +43,13 @@ function addActionsButtons(actionsArea, TryAgainButton, chatConf) {
downloadThread({ as: Format.PDF });
};
actionsArea.appendChild(downloadPdfButton);
if (new RegExp('//chat.openai.com').test(chatConf.origin)) {
const exportHtml = TryAgainButton.cloneNode(true);
exportHtml.id = "download-html-button";
exportHtml.innerText = "Share Link";
exportHtml.onclick = () => {
sendRequest();
};
actionsArea.appendChild(exportHtml);
}
const exportHtml = TryAgainButton.cloneNode(true);
exportHtml.id = "download-html-button";
exportHtml.innerText = "Share Link";
exportHtml.onclick = () => {
sendRequest();
};
actionsArea.appendChild(exportHtml);
}
function removeButtons() {

View File

@@ -1,77 +0,0 @@
function init() {
document.body.innerHTML = `<style>
body {
height: 100vh;
padding: 0;
margin: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-family: Söhne,ui-sans-serif,system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,sans-serif,Helvetica Neue,Arial,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;
}
h3 {
margin-bottom: 20px;
}
input {
all: unset;
width: 280px;
height: 30px;
margin-bottom: 10px;
padding: 0 5px;
border: solid 2px #d8d8d8;
background-color: #fff;
border-radius: 5px !important;
color: #4a4a4a;
}
button {
all: unset;
height: 30px;
font-size: 16px;
padding: 0 10px;
line-height: 30px;
margin: 0 5px;
color: #fff;
border-radius: 5px;
cursor: pointer;
}
#cancel {
background-color: #999;
}
#confirm {
background-color: #10a37f;
}
</style>
<h3>Switch Origin</h3>
<input id="input" type="text" autocapitalize="off" autocomplete="off" spellcheck="false" autofocus placeholder="https://chat.openai.com" />
<div class="btns">
<button id="cancel">Cancel</button>
<button id="confirm">Confirm</button>
</div>`;
const srcipt = document.createElement('script');
srcipt.innerHTML = `const input = document.getElementById('input');
const cancelBtn = document.getElementById('cancel');
const confirmBtn = document.getElementById('confirm');
cancelBtn.addEventListener('click', () => {
window.invoke('form_cancel', { label: 'origin', title: 'Switch Origin', msg: 'Are you sure you want to cancel editing?' });
})
confirmBtn.addEventListener('click', () => {
if (/^https?:\\/\\//.test(input.value)) {
window.invoke('form_confirm', { data: { origin: input.value } });
} else {
window.invoke('form_msg', { label: 'origin', title: 'Switch Origin', msg: 'Invalid URL!' });
}
})`;
document.head.appendChild(srcipt);
}
// run init
if (
document.readyState === "complete" ||
document.readyState === "interactive"
) {
init();
} else {
document.addEventListener("DOMContentLoaded", init);
}

View File

@@ -1,89 +0,0 @@
function init() {
document.body.innerHTML = `<style>
body {
height: 100vh;
padding: 0;
margin: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-family: Söhne,ui-sans-serif,system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,sans-serif,Helvetica Neue,Arial,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;
}
h3 {
margin-bottom: 20px;
}
textarea {
all: unset;
width: 300px;
height: 60px;
margin-bottom: 10px;
padding: 10px;
border: solid 2px #d8d8d8;
background-color: #fff;
border-radius: 5px !important;
color: #4a4a4a;
}
button {
all: unset;
height: 30px;
font-size: 16px;
padding: 0 10px;
line-height: 30px;
margin: 0 5px;
color: #fff;
border-radius: 5px;
cursor: pointer;
}
#cancel {
background-color: #999;
}
#confirm {
background-color: #10a37f;
}
.item {
display: flex;
align-items: center;
}
label {
width: 120px;
margin-right: 10px;
}
</style>
<h3>User Agent</h3>
<div class="item">
<label>Main Window (PC)</label>
<textarea id="ua_pc" type="text" autocapitalize="off" autocomplete="off" spellcheck="false" autofocus placeholder="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ..."></textarea>
</div>
<div class="item">
<label>Tray Window (Phone)</label>
<textarea id="ua_phone" type="text" autocapitalize="off" autocomplete="off" spellcheck="false" autofocus placeholder="Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS ..."></textarea>
</div>
<div class="btns">
<button id="cancel">Cancel</button>
<button id="confirm">Confirm</button>
</div>`;
const srcipt = document.createElement('script');
srcipt.innerHTML = `const ua_pc = document.getElementById('ua_pc');
const ua_phone = document.getElementById('ua_phone');
const cancelBtn = document.getElementById('cancel');
const confirmBtn = document.getElementById('confirm');
cancelBtn.addEventListener('click', () => {
window.invoke('form_cancel', { label: 'ua', title: 'User Agent', msg: 'Are you sure you want to cancel editing?' });
})
confirmBtn.addEventListener('click', () => {
window.invoke('form_confirm', { data: { ua_pc: ua_pc.value, ua_phone: ua_phone.value } });
})`;
document.head.appendChild(srcipt);
}
// run init
if (
document.readyState === "complete" ||
document.readyState === "interactive"
) {
init();
} else {
document.addEventListener("DOMContentLoaded", init);
}

View File

@@ -1,43 +1,18 @@
use crate::utils::{chat_root, create_file, exists};
use anyhow::Result;
use serde_json::Value;
use std::{collections::BTreeMap, fs, path::PathBuf, sync::Mutex};
use tauri::Theme;
#[cfg(target_os = "macos")]
use tauri::TitleBarStyle;
// pub const USER_AGENT: &str = "5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36";
// pub const PHONE_USER_AGENT: &str = "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1";
use std::fs;
use std::path::PathBuf;
use std::sync::Mutex;
pub const USER_AGENT: &str = "5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36";
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 DEFAULT_CHAT_CONF: &str = r#"{
"always_on_top": false,
"theme": "Light",
"titlebar": true,
"default_origin": "https://chat.openai.com",
"origin": "https://chat.openai.com",
"ua_pc": "",
"ua_phone": ""
}"#;
pub const DEFAULT_CHAT_CONF_MAC: &str = r#"{
"always_on_top": false,
"theme": "Light",
"titlebar": false,
"default_origin": "https://chat.openai.com",
"origin": "https://chat.openai.com",
"ua_pc": "",
"ua_phone": ""
}"#;
pub struct ChatState {
pub always_on_top: Mutex<bool>,
}
impl ChatState {
pub fn default(chat_conf: ChatConfJson) -> Self {
pub fn default(chat_conf: &ChatConfJson) -> Self {
ChatState {
always_on_top: Mutex::new(chat_conf.always_on_top),
}
@@ -46,13 +21,7 @@ impl ChatState {
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
pub struct ChatConfJson {
pub titlebar: bool,
pub always_on_top: bool,
pub theme: String,
pub default_origin: String,
pub origin: String,
pub ua_pc: String,
pub ua_phone: String,
}
impl ChatConfJson {
@@ -62,12 +31,7 @@ impl ChatConfJson {
let conf_file = ChatConfJson::conf_path();
if !exists(&conf_file) {
create_file(&conf_file).unwrap();
#[cfg(target_os = "macos")]
fs::write(&conf_file, DEFAULT_CHAT_CONF_MAC).unwrap();
#[cfg(not(target_os = "macos"))]
fs::write(&conf_file, DEFAULT_CHAT_CONF).unwrap();
fs::write(&conf_file, r#"{"always_on_top": false}"#).unwrap();
}
conf_file
}
@@ -77,51 +41,26 @@ impl ChatConfJson {
}
pub fn get_chat_conf() -> Self {
let config_file = fs::read_to_string(ChatConfJson::conf_path())
.unwrap_or_else(|_| DEFAULT_CHAT_CONF.to_string());
let config: Value =
let config_file = fs::read_to_string(ChatConfJson::conf_path()).unwrap();
let config: serde_json::Value =
serde_json::from_str(&config_file).expect("failed to parse chat.conf.json");
serde_json::from_value(config).unwrap_or_else(|_| ChatConfJson::chat_conf_default())
}
// https://users.rust-lang.org/t/updating-object-fields-given-dynamic-json/39049/3
pub fn amend(new_rules: &Value) -> Result<()> {
let config = ChatConfJson::get_chat_conf();
let config: Value = serde_json::to_value(&config)?;
let mut config: BTreeMap<String, Value> = serde_json::from_value(config)?;
let new_rules: BTreeMap<String, Value> = serde_json::from_value(new_rules.clone())?;
for (k, v) in new_rules {
config.insert(k, v);
}
pub fn update_chat_conf(always_on_top: bool) {
let mut conf = ChatConfJson::get_chat_conf();
conf.always_on_top = always_on_top;
fs::write(
ChatConfJson::conf_path(),
serde_json::to_string_pretty(&config)?,
)?;
Ok(())
}
pub fn theme() -> Option<Theme> {
let conf = ChatConfJson::get_chat_conf();
if conf.theme == "Dark" {
Some(Theme::Dark)
} else {
Some(Theme::Light)
}
}
#[cfg(target_os = "macos")]
pub fn titlebar() -> TitleBarStyle {
let conf = ChatConfJson::get_chat_conf();
if conf.titlebar {
TitleBarStyle::Transparent
} else {
TitleBarStyle::Overlay
}
serde_json::to_string(&conf).unwrap(),
)
.unwrap();
}
pub fn chat_conf_default() -> Self {
serde_json::from_value(serde_json::json!(DEFAULT_CHAT_CONF)).unwrap()
serde_json::from_value(serde_json::json!({
"always_on_top": false,
}))
.unwrap()
}
}

View File

@@ -8,28 +8,24 @@ mod conf;
mod utils;
use app::{cmd, menu, setup};
use conf::{ChatConfJson, ChatState};
use conf::ChatConfJson;
fn main() {
ChatConfJson::init();
let chat_conf = ChatConfJson::get_chat_conf();
let context = tauri::generate_context!();
let chat_conf = ChatConfJson::get_chat_conf();
let chat_conf2 = chat_conf.clone();
tauri::Builder::default()
.manage(ChatState::default(chat_conf))
.manage(conf::ChatState::default(&chat_conf))
.invoke_handler(tauri::generate_handler![
cmd::drag_window,
cmd::fullscreen,
cmd::download,
cmd::open_link,
cmd::get_chat_conf,
cmd::form_cancel,
cmd::form_confirm,
cmd::form_msg,
cmd::open_link
])
.setup(setup::init)
.plugin(tauri_plugin_positioner::init())
.menu(menu::init(&context))
.setup(|app| setup::init(app, chat_conf2))
.menu(menu::init(&chat_conf, &context))
.system_tray(menu::tray_menu())
.on_menu_event(menu::menu_handler)
.on_system_tray_event(menu::tray_handler)

View File

@@ -4,19 +4,18 @@ use std::{
path::{Path, PathBuf},
process::Command,
};
use tauri::Manager;
// use tauri::utils::config::Config;
use tauri::utils::config::Config;
pub fn chat_root() -> PathBuf {
tauri::api::path::home_dir().unwrap().join(".chatgpt")
}
// pub fn get_tauri_conf() -> Option<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 get_tauri_conf() -> Option<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()
@@ -62,20 +61,3 @@ pub fn open_file(path: PathBuf) {
#[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());
}
},
);
}

View File

@@ -2,20 +2,20 @@
"build": {
"beforeDevCommand": "",
"beforeBuildCommand": "",
"devPath": "../dist",
"devPath": "https://chat.openai.com/",
"distDir": "../dist"
},
"package": {
"productName": "ChatGPT",
"version": "0.2.1"
"version": "0.1.6"
},
"tauri": {
"allowlist": {
"all": true
},
"systemTray": {
"iconPath": "icons/tray-icon.png",
"iconAsTemplate": true
"iconPath": "icons/icon.png",
"iconAsTemplate": false
},
"bundle": {
"active": true,