mirror of
https://github.com/FranP-code/ChatGPT.git
synced 2025-10-13 00:13:25 +00:00
Compare commits
50 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d0d2869dff | ||
|
|
70881f95da | ||
|
|
60f6628ec0 | ||
|
|
60abf6e487 | ||
|
|
216ef3ff1a | ||
|
|
7b12d3ebfe | ||
|
|
396dc0b762 | ||
|
|
cd7d5fab63 | ||
|
|
8363ff234d | ||
|
|
430d6e4af2 | ||
|
|
918d53c9b9 | ||
|
|
a88c3603cc | ||
|
|
785be23528 | ||
|
|
bb44cdd5b7 | ||
|
|
5a835c114e | ||
|
|
3ea8f44452 | ||
|
|
04a579ff19 | ||
|
|
f08cd978cb | ||
|
|
7cf20cb571 | ||
|
|
9f26e854df | ||
|
|
4bedb8b5c1 | ||
|
|
dea81eb3d9 | ||
|
|
9e5dff5dab | ||
|
|
2a3e252eb0 | ||
|
|
0da9aef346 | ||
|
|
fd221815a6 | ||
|
|
410c1c597c | ||
|
|
15610ae1d3 | ||
|
|
bc940d97db | ||
|
|
92dc316c4f | ||
|
|
36e7be2c29 | ||
|
|
f1f58f37c4 | ||
|
|
5e295aeb1d | ||
|
|
325dbb305c | ||
|
|
55e17e1e5f | ||
|
|
ae21f2f6a8 | ||
|
|
474670b13f | ||
|
|
0e7fb47c4f | ||
|
|
35353f3564 | ||
|
|
4d363c3847 | ||
|
|
780a23b08f | ||
|
|
78f8daab86 | ||
|
|
3eac43541e | ||
|
|
050b7010fc | ||
|
|
087160861c | ||
|
|
690736b5b0 | ||
|
|
abc8d28c01 | ||
|
|
3fa221b811 | ||
|
|
0cd76ba2fb | ||
|
|
a3791f5c86 |
4
.gitattributes
vendored
4
.gitattributes
vendored
@@ -1 +1,3 @@
|
||||
*.js linguist-vendored
|
||||
*.js linguist-vendored
|
||||
*.tsx linguist-vendored
|
||||
*.scss linguist-vendored
|
||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -75,7 +75,7 @@ jobs:
|
||||
${{ runner.os }}-yarn-
|
||||
|
||||
- name: Install app dependencies and build it
|
||||
run: yarn
|
||||
run: yarn && yarn build:fe
|
||||
|
||||
- uses: tauri-apps/tauri-action@v0.3
|
||||
env:
|
||||
|
||||
29
.gitignore
vendored
29
.gitignore
vendored
@@ -1,9 +1,32 @@
|
||||
.DS_Store
|
||||
*.lock
|
||||
|
||||
package-lock.json
|
||||
node_modules/
|
||||
yarn.lock
|
||||
*.lock
|
||||
|
||||
# rust
|
||||
target/
|
||||
Cargo.lock
|
||||
|
||||
# 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?
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# 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
|
||||
@@ -9,6 +10,7 @@
|
||||
- [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.
|
||||
- [ChatGPT-Google](https://github.com/ZohaibAhmed/ChatGPT-Google) - Chrome Extension that Integrates ChatGPT (Unofficial) into Google Search
|
||||
|
||||
`VSCode`
|
||||
|
||||
@@ -17,3 +19,8 @@
|
||||
`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
|
||||
|
||||
## Tools
|
||||
|
||||
- [commitgpt](https://github.com/RomanHotsiy/commitgpt) - Automatically generate commit messages using ChatGPT
|
||||
- [ShareGPT](https://sharegpt.com/) - ShareGPT: Share your wildest ChatGPT conversations with one click.
|
||||
|
||||
214
LICENSE
214
LICENSE
@@ -1,21 +1,201 @@
|
||||
MIT License
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
Copyright (c) 2022 lencx
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
1. Definitions.
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
154
README-ZH.md
Normal file
154
README-ZH.md
Normal file
@@ -0,0 +1,154 @@
|
||||
<p align="center">
|
||||
<img width="180" src="./public/logo.png" alt="ChatGPT">
|
||||
<h1 align="center">ChatGPT</h1>
|
||||
</p>
|
||||
|
||||
> ChatGPT 桌面应用
|
||||
|
||||
[](./README.md)
|
||||
[](./README-ZH.md)
|
||||
[](https://github.com/lencx/ChatGPT/releases)
|
||||
[](https://twitter.com/lencx_)
|
||||
|
||||
[Awesome ChatGPT](./AWESOME.md)
|
||||
|
||||
## 📦 下载
|
||||
|
||||
[📝 更新日志](./UPDATE_LOG.md)
|
||||
|
||||
<!-- download start -->
|
||||
|
||||
**最新版:**
|
||||
|
||||
- `Mac`: [ChatGPT_0.2.1_x64.dmg](https://github.com/lencx/ChatGPT/releases/download/v0.2.1/ChatGPT_0.2.1_x64.dmg)
|
||||
- `Linux`: [chat-gpt_0.2.1_amd64.deb](https://github.com/lencx/ChatGPT/releases/download/v0.2.1/chat-gpt_0.2.1_amd64.deb)
|
||||
- `Windows`: [ChatGPT_0.2.1_x64_en-US.msi](https://github.com/lencx/ChatGPT/releases/download/v0.2.1/ChatGPT_0.2.1_x64_en-US.msi)
|
||||
|
||||
[其他版本...](https://github.com/lencx/ChatGPT/releases)
|
||||
|
||||
<!-- download end -->
|
||||
|
||||
### brew 安装
|
||||
|
||||
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 }
|
||||
~~~
|
||||
|
||||
## ✨ 功能概览
|
||||
|
||||
- 跨平台: `macOS` `Linux` `Windows`
|
||||
- 导出 ChatGPT 聊天记录 (支持 PNG, PDF 和生成分享链接)
|
||||
- 应用自动升级通知
|
||||
- 丰富的快捷键
|
||||
- 系统托盘悬浮窗
|
||||
- 应用菜单功能强大
|
||||
|
||||
### 菜单项
|
||||
|
||||
- **Preferences (喜好)**
|
||||
- `Theme` - `Light`, `Dark` (仅支持 macOS 和 Windows)
|
||||
- `Always On Top`: 窗口置顶
|
||||
- `Titlebar`: 是否显示 `Titlebar`,仅 macOS 支持
|
||||
- `Inject Script`: 用于修改网站的用户自定义脚本
|
||||
- `Control Center`: ChatGPT 应用的控制中心,它将为应用提供无限的可能
|
||||
- 设置 `Theme`,`Always on Top`,`Titlebar` 等
|
||||
- `User Agent` ([#17](https://github.com/lencx/ChatGPT/issues/17)): 自定义 `user agent` 防止网站安全检测,默认值为空
|
||||
- `Switch Origin` ([#14](https://github.com/lencx/ChatGPT/issues/14)): 切换网站源地址,默认为 `https://chat.openai.com`。需要注意的是镜像网站的 UI 需要和原网站一致,否则可能会导致某些功能不工作
|
||||
- `Go to Config`: 打开 ChatGPT 配置目录 (`path: ~/.chatgpt/*`)
|
||||
- `Clear Config`: 清除 ChatGPT 配置数据 (`path: ~/.chatgpt/*`), 这是危险操作,请提前备份数据
|
||||
- `Restart ChatGPT`: 重启应用。如果注入脚本编辑完成,或者应用可卡死可以通过此菜单重新启动应用
|
||||
- `Awesome ChatGPT`: 一个很棒的 ChatGPT 推荐列表
|
||||
- **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 应用更新日志
|
||||
- `Report Bug`: 报告 BUG 或反馈建议
|
||||
- `Toggle Developer Tools`: 网站调试工具,调试页面或脚本可能需要
|
||||
|
||||
## 👀 预览
|
||||
|
||||
<img width="320" src="./assets/install.png" alt="install"> <img width="320" src="./assets/chat-control-center.png" alt="chat control center">
|
||||
<img width="320" src="./assets/export.png" alt="export"> <img width="320" src="./assets/tray.png" alt="tray">
|
||||
<img width="320" src="./assets/chat-tray-login.png" alt="chat tray login"> <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>
|
||||
|
||||
## 国内用户
|
||||
|
||||
国内用户如果遇到使用问题或者想交流 ChatGPT 技巧,可以关注公众号“浮之静”,发送 “chat” 进群参与讨论。如果对 tauri 开发应用感兴趣可以关注公众号后回复 “tauri” 进技术开发群(想私聊的也可以关注公众号,来添加微信)。
|
||||
|
||||
<img width="180" src="https://user-images.githubusercontent.com/16164244/207228300-ea5c4688-c916-4c55-a8c3-7f862888f351.png"> <img width="200" src="https://user-images.githubusercontent.com/16164244/207228025-117b5f77-c5d2-48c2-a070-774b7a1596f2.png">
|
||||
|
||||
## ❓常见问题
|
||||
|
||||
### 不能打开 ChatGPT
|
||||
|
||||
如果升级应用后无法打开,请尝试清除配置,它位于此目录 `~/.chatgpt/*`。
|
||||
|
||||
### 主窗口已经登录,但是系统托盘窗口显示未登录
|
||||
|
||||
可通过菜单项里的 `Restart ChatGPT` 重启应用来修复这个问题(`Menu -> Preferences -> Restart ChatGPT`)。
|
||||
|
||||
### 它是否安全?
|
||||
|
||||
它是安全的,仅仅只是对 [OpenAI ChatGPT](https://chat.openai.com) 网站的包装,注入了一些额外功能(均在本地,未发起网络请求),如果存疑,可以检查源代码。
|
||||
|
||||
### Developer cannot be verified?
|
||||
|
||||
Mac 上无法安装,提示开发者未验证,具体可以查看下面给出的解决方案(它是开源的,很安全)。
|
||||
|
||||
- [Open a Mac app from an unidentified developer](https://support.apple.com/en-sg/guide/mac-help/mh40616/mac)
|
||||
|
||||
### 我想自己构建它?
|
||||
|
||||
#### 预安装
|
||||
|
||||
- [Rust](https://www.rust-lang.org/)
|
||||
- [VS Code](https://code.visualstudio.com/)
|
||||
- [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer)
|
||||
- [tauri](https://marketplace.visualstudio.com/items?itemName=tauri-apps.tauri-vscode)
|
||||
|
||||
#### 开始
|
||||
|
||||
```bash
|
||||
# step1: 克隆仓库
|
||||
git clone https://github.com/lencx/ChatGPT.git
|
||||
|
||||
# step2: 进入目录
|
||||
cd ChatGPT
|
||||
|
||||
# step3: 安装依赖
|
||||
yarn
|
||||
|
||||
# step4: 开发启动
|
||||
yarn dev
|
||||
|
||||
# step5: 构建应用
|
||||
# 构建后的安装包位置: src-tauri/target/release/bundle
|
||||
yarn build
|
||||
```
|
||||
|
||||
## ❤️ 感谢
|
||||
|
||||
- 分享按钮的代码从 [@liady](https://github.com/liady) 的插件获得,并做了一些本地化修改
|
||||
|
||||
---
|
||||
|
||||
[](https://star-history.com/#lencx/chatgpt&Date)
|
||||
|
||||
## License
|
||||
|
||||
Apache License
|
||||
115
README.md
115
README.md
@@ -1,45 +1,106 @@
|
||||
<p align="center">
|
||||
<img width="180" src="./logo.png" alt="ChatGPT">
|
||||
<img width="180" src="./public/logo.png" alt="ChatGPT">
|
||||
<h1 align="center">ChatGPT</h1>
|
||||
</p>
|
||||
|
||||
> ChatGPT Desktop Application
|
||||
|
||||
[](./README.md)
|
||||
[](./README-ZH.md)
|
||||
[](https://github.com/lencx/ChatGPT/releases)
|
||||
[](https://twitter.com/lencx_)
|
||||
|
||||
[Awesome ChatGPT](./AWESOME.md)
|
||||
|
||||
## Downloads
|
||||
## 📦 Downloads
|
||||
|
||||
[](https://github.com/lencx/ChatGPT/releases)
|
||||
[📝 Update Log](./UPDATE_LOG.md)
|
||||
|
||||
<!-- download start -->
|
||||
|
||||
**Latest:**
|
||||
|
||||
- `Mac`: [ChatGPT_0.1.7_x64.dmg](https://github.com/lencx/ChatGPT/releases/download/v0.1.7/ChatGPT_0.1.7_x64.dmg)
|
||||
- `Linux`: [chat-gpt_0.1.7_amd64.deb](https://github.com/lencx/ChatGPT/releases/download/v0.1.7/chat-gpt_0.1.7_amd64.deb)
|
||||
- `Windows`: [ChatGPT_0.1.7_x64_en-US.msi](https://github.com/lencx/ChatGPT/releases/download/v0.1.7/ChatGPT_0.1.7_x64_en-US.msi)
|
||||
- `Mac`: [ChatGPT_0.2.1_x64.dmg](https://github.com/lencx/ChatGPT/releases/download/v0.2.1/ChatGPT_0.2.1_x64.dmg)
|
||||
- `Linux`: [chat-gpt_0.2.1_amd64.deb](https://github.com/lencx/ChatGPT/releases/download/v0.2.1/chat-gpt_0.2.1_amd64.deb)
|
||||
- `Windows`: [ChatGPT_0.2.1_x64_en-US.msi](https://github.com/lencx/ChatGPT/releases/download/v0.2.1/ChatGPT_0.2.1_x64_en-US.msi)
|
||||
|
||||
[Other version...](https://github.com/lencx/ChatGPT/releases)
|
||||
|
||||
<!-- download end -->
|
||||
|
||||
## Features
|
||||
### Install
|
||||
|
||||
- multi-platform: `macOS` `Linux` `Windows`
|
||||
- export ChatGPT history (PNG, PDF and Share Link)
|
||||
- always on top (whether the window should always be on top of other windows)
|
||||
- inject script
|
||||
- auto updater
|
||||
- app menu
|
||||
- tray window
|
||||
- shortcut
|
||||
Easily install with _[Homebrew](https://brew.sh) ([Cask](https://docs.brew.sh/Cask-Cookbook)):_
|
||||
|
||||
## Preview
|
||||
~~~ sh
|
||||
brew tap lencx/chatgpt https://github.com/lencx/ChatGPT.git
|
||||
brew install --cask chatgpt --no-quarantine
|
||||
~~~
|
||||
|
||||
<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/tray.png" alt="tray">
|
||||
<img width="360" src="./assets/auto-update.png" alt="auto update">
|
||||
## FAQ
|
||||
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
|
||||
|
||||
- Multi-platform: `macOS` `Linux` `Windows`
|
||||
- Export ChatGPT history (PNG, PDF and Share Link)
|
||||
- Automatic application upgrade notification
|
||||
- Common shortcut keys
|
||||
- System tray hover window
|
||||
- Powerful menu items
|
||||
|
||||
### MenuItem
|
||||
|
||||
- **Preferences**
|
||||
- `Theme` - `Light`, `Dark` (Only macOS and Windows are supported).
|
||||
- `Always on Top`: The window is always on top of other windows.
|
||||
- `Titlebar`: Whether to display the titlebar, supported by macOS only.
|
||||
- `Inject Script`: Using scripts to modify pages.
|
||||
- `Control Center`: The control center of ChatGPT application, it will give unlimited imagination to the application.
|
||||
- `Theme`, `Always on Top`, `Titlebar`, ...
|
||||
- `User Agent` ([#17](https://github.com/lencx/ChatGPT/issues/17)): Custom `user agent`, which may be required in some scenarios. The default value is the empty string.
|
||||
- `Switch Origin` ([#14](https://github.com/lencx/ChatGPT/issues/14)): Switch the site source address, the default is `https://chat.openai.com`, please make sure the mirror site UI is the same as the original address. Otherwise, some functions may not be available.
|
||||
- `Go to Config`: Open the configuration file directory (`path: ~/.chatgpt/*`).
|
||||
- `Clear Config`: Clear the configuration file (`path: ~/.chatgpt/*`), dangerous operation, please backup the data in advance.
|
||||
- `Restart ChatGPT`: Restart the application, for example: the program is stuck or the injection script can take effect by restarting the application after editing.
|
||||
- `Awesome ChatGPT`: Recommended Related Resources.
|
||||
- **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 changelog.
|
||||
- `Report Bug`: Report a bug or give feedback.
|
||||
- `Toggle Developer Tools`: Developer debugging tools.
|
||||
|
||||
## TODO
|
||||
|
||||
- Web access capability ([#20](https://github.com/lencx/ChatGPT/issues/20))
|
||||
- Shortcut command typing chatgpt prompt
|
||||
- ...
|
||||
|
||||
## 👀 Preview
|
||||
|
||||
<img width="320" src="./assets/install.png" alt="install"> <img width="320" src="./assets/chat-control-center.png" alt="chat control center">
|
||||
<img width="320" src="./assets/export.png" alt="export"> <img width="320" src="./assets/tray.png" alt="tray">
|
||||
<img width="320" src="./assets/chat-tray-login.png" alt="chat tray login"> <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 you cannot open the application after the upgrade, please try to clear the configuration file, which is in the `~/.chatgpt/*` directory.
|
||||
|
||||
## Out of sync login status between multiple windows
|
||||
|
||||
If you have already logged in in the main window, but the system tray window shows that you are not logged in, you can fix it by restarting the application (`Menu -> Preferences -> Restart ChatGPT`).
|
||||
|
||||
### Is it safe?
|
||||
|
||||
@@ -78,8 +139,14 @@ yarn dev
|
||||
yarn build
|
||||
```
|
||||
|
||||
## Related
|
||||
## ❤️ Thanks
|
||||
|
||||
- [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.
|
||||
- The core implementation of the share button code was copied from the [@liady](https://github.com/liady) extension with some modifications.
|
||||
|
||||
---
|
||||
|
||||
[](https://star-history.com/#lencx/chatgpt&Date)
|
||||
|
||||
## License
|
||||
|
||||
Apache License
|
||||
|
||||
@@ -1,5 +1,34 @@
|
||||
# UPDATE LOG
|
||||
|
||||
## v0.3.0
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
BIN
assets/chat-control-center.png
Normal file
BIN
assets/chat-control-center.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 500 KiB |
BIN
assets/chat-tray-login.png
Normal file
BIN
assets/chat-tray-login.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 989 KiB |
BIN
assets/chat.png
BIN
assets/chat.png
Binary file not shown.
|
Before Width: | Height: | Size: 653 KiB |
22
casks/chatgpt.rb
Normal file
22
casks/chatgpt.rb
Normal file
@@ -0,0 +1,22 @@
|
||||
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
|
||||
0
dist/.gitkeep
vendored
0
dist/.gitkeep
vendored
13
index.html
Normal file
13
index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>ChatGPT</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
33
package.json
33
package.json
@@ -2,6 +2,8 @@
|
||||
"name": "chatgpt",
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"dev:fe": "vite",
|
||||
"build:fe": "tsc && vite build",
|
||||
"dev": "yarn tauri dev",
|
||||
"build": "yarn tauri build",
|
||||
"updater": "tr updater",
|
||||
@@ -12,15 +14,40 @@
|
||||
},
|
||||
"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": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/lencx/ChatGPT"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^1.2.0",
|
||||
"antd": "^5.0.6",
|
||||
"lodash": "^4.17.21",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-router-dom": "^6.4.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tauri-apps/cli": "^1.2.1",
|
||||
"@tauri-release/cli": "^0.2.3"
|
||||
"@tauri-apps/cli": "^1.2.2",
|
||||
"@tauri-release/cli": "^0.2.3",
|
||||
"@types/lodash": "^4.14.191",
|
||||
"@types/node": "^18.7.10",
|
||||
"@types/react": "^18.0.15",
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"@vitejs/plugin-react": "^3.0.0",
|
||||
"sass": "^1.56.2",
|
||||
"typescript": "^4.9.4",
|
||||
"vite": "^4.0.0",
|
||||
"vite-tsconfig-paths": "^4.0.2"
|
||||
}
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
@@ -14,11 +14,11 @@ 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.1", features = ["api-all", "devtools", "system-tray", "updater"] }
|
||||
tauri = { version = "1.2.2", features = ["api-all", "devtools", "system-tray", "updater"] }
|
||||
tauri-plugin-positioner = { version = "1.0.4", features = ["system-tray"] }
|
||||
anyhow = "1.0.66"
|
||||
|
||||
[features]
|
||||
# by default Tauri runs in production mode
|
||||
|
||||
BIN
src-tauri/icons/tray-icon.png
Normal file
BIN
src-tauri/icons/tray-icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 76 KiB |
@@ -1,4 +1,4 @@
|
||||
use crate::utils;
|
||||
use crate::{conf::ChatConfJson, utils};
|
||||
use std::fs;
|
||||
use tauri::{api, command, AppHandle, Manager};
|
||||
|
||||
@@ -28,3 +28,34 @@ 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), 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();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[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);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
use crate::{conf, utils};
|
||||
use crate::{
|
||||
conf::{self, ChatConfJson},
|
||||
utils,
|
||||
};
|
||||
use tauri::{
|
||||
utils::assets::EmbeddedAssets, AboutMetadata, AppHandle, Context, CustomMenuItem, Manager,
|
||||
Menu, MenuItem, Submenu, SystemTray, SystemTrayEvent, SystemTrayMenu, WindowMenuEvent,
|
||||
@@ -6,13 +9,14 @@ use tauri::{
|
||||
use tauri_plugin_positioner::{on_tray_event, Position, WindowExt};
|
||||
|
||||
// --- Menu
|
||||
pub fn init(chat_conf: &conf::ChatConfJson, context: &Context<EmbeddedAssets>) -> Menu {
|
||||
pub fn init(context: &Context<EmbeddedAssets>) -> Menu {
|
||||
let chat_conf = ChatConfJson::get_chat_conf();
|
||||
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::Separator)
|
||||
.add_native_item(MenuItem::Services)
|
||||
.add_native_item(MenuItem::Separator)
|
||||
.add_native_item(MenuItem::Hide)
|
||||
.add_native_item(MenuItem::HideOthers)
|
||||
@@ -21,26 +25,68 @@ pub fn init(chat_conf: &conf::ChatConfJson, context: &Context<EmbeddedAssets>) -
|
||||
.add_native_item(MenuItem::Quit),
|
||||
);
|
||||
|
||||
let always_on_top = CustomMenuItem::new("always_on_top".to_string(), "Always On Top")
|
||||
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::new()
|
||||
.add_item(
|
||||
CustomMenuItem::new("inject_script".to_string(), "Inject Script")
|
||||
.accelerator("CmdOrCtrl+J"),
|
||||
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
|
||||
}),
|
||||
)
|
||||
.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"),
|
||||
),
|
||||
.into(),
|
||||
always_on_top_menu.into(),
|
||||
#[cfg(target_os = "macos")]
|
||||
titlebar_menu.into(),
|
||||
MenuItem::Separator.into(),
|
||||
CustomMenuItem::new("inject_script".to_string(), "Inject Script")
|
||||
.accelerator("CmdOrCtrl+J")
|
||||
.into(),
|
||||
CustomMenuItem::new("control_center".to_string(), "Control Center")
|
||||
.accelerator("CmdOrCtrl+Shift+P")
|
||||
.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(),
|
||||
]),
|
||||
);
|
||||
|
||||
let edit_menu = Submenu::new(
|
||||
@@ -73,6 +119,7 @@ pub fn init(chat_conf: &conf::ChatConfJson, context: &Context<EmbeddedAssets>) -
|
||||
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")
|
||||
@@ -83,6 +130,7 @@ pub fn init(chat_conf: &conf::ChatConfJson, context: &Context<EmbeddedAssets>) -
|
||||
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")
|
||||
@@ -111,8 +159,29 @@ pub fn menu_handler(event: WindowMenuEvent<tauri::Wry>) {
|
||||
|
||||
match menu_id {
|
||||
// Preferences
|
||||
"control_center" => app.get_window("main").unwrap().show().unwrap(),
|
||||
"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()),
|
||||
"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());
|
||||
}
|
||||
"theme_light" | "theme_dark" => {
|
||||
let theme = if menu_id == "theme_dark" {
|
||||
"Dark"
|
||||
} else {
|
||||
"Light"
|
||||
};
|
||||
ChatConfJson::amend(&serde_json::json!({ "theme": theme }), Some(app)).unwrap();
|
||||
}
|
||||
"always_on_top" => {
|
||||
let mut always_on_top = state.always_on_top.lock().unwrap();
|
||||
*always_on_top = !*always_on_top;
|
||||
@@ -121,7 +190,11 @@ pub fn menu_handler(event: WindowMenuEvent<tauri::Wry>) {
|
||||
.set_selected(*always_on_top)
|
||||
.unwrap();
|
||||
win.set_always_on_top(*always_on_top).unwrap();
|
||||
conf::ChatConfJson::update_chat_conf(*always_on_top);
|
||||
ChatConfJson::amend(
|
||||
&serde_json::json!({ "always_on_top": *always_on_top }),
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
// View
|
||||
"reload" => win.eval("window.location.reload()").unwrap(),
|
||||
@@ -145,6 +218,7 @@ 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();
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
use crate::{app::window, conf, utils};
|
||||
use crate::{app::window, conf::ChatConfJson, utils};
|
||||
use tauri::{utils::config::WindowUrl, window::WindowBuilder, App, Manager};
|
||||
|
||||
#[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();
|
||||
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")]
|
||||
@@ -18,14 +13,15 @@ pub fn init(
|
||||
.fullscreen(false)
|
||||
.inner_size(800.0, 600.0)
|
||||
.hidden_title(true)
|
||||
.title_bar_style(TitleBarStyle::Overlay)
|
||||
.theme(theme)
|
||||
.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(conf::USER_AGENT)
|
||||
.user_agent(&chat_conf.ua_window)
|
||||
.build()?;
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
@@ -34,13 +30,14 @@ pub fn init(
|
||||
.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(conf::USER_AGENT)
|
||||
.user_agent(&chat_conf.ua_window)
|
||||
.build()?;
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -2,22 +2,22 @@ use crate::{conf, utils};
|
||||
use tauri::{utils::config::WindowUrl, window::WindowBuilder};
|
||||
|
||||
pub fn mini_window(handle: &tauri::AppHandle) {
|
||||
let conf = utils::get_tauri_conf().unwrap();
|
||||
let url = conf.build.dev_path.to_string();
|
||||
let chat_conf = conf::ChatConfJson::get_chat_conf();
|
||||
let theme = conf::ChatConfJson::theme();
|
||||
|
||||
WindowBuilder::new(handle, "mini", WindowUrl::App(url.into()))
|
||||
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(conf::PHONE_USER_AGENT)
|
||||
.menu(tauri::Menu::new())
|
||||
.user_agent(&chat_conf.ua_tray)
|
||||
.build()
|
||||
.unwrap()
|
||||
.hide()
|
||||
|
||||
6
src-tauri/src/assets/core.js
vendored
6
src-tauri/src/assets/core.js
vendored
@@ -1,4 +1,5 @@
|
||||
// *** Core Script - IPC ***
|
||||
|
||||
const uid = () => window.crypto.getRandomValues(new Uint32Array(1))[0];
|
||||
function transformCallback(callback = () => {}, once = false) {
|
||||
const identifier = uid();
|
||||
@@ -54,7 +55,8 @@ async function init() {
|
||||
}
|
||||
|
||||
const _platform = await platform();
|
||||
if (/darwin/.test(_platform)) {
|
||||
const chatConf = await invoke('get_chat_conf') || {};
|
||||
if (/darwin/.test(_platform) && !chatConf.titlebar) {
|
||||
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);
|
||||
@@ -70,7 +72,7 @@ async function init() {
|
||||
document.addEventListener("click", (e) => {
|
||||
const origin = e.target.closest("a");
|
||||
if (origin && origin.href && origin.target !== '_self') {
|
||||
origin.target = "_self";
|
||||
invoke('open_link', { url: origin.href });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
43
src-tauri/src/assets/export.js
vendored
43
src-tauri/src/assets/export.js
vendored
@@ -1,7 +1,8 @@
|
||||
// *** Core Script - Export ***
|
||||
// @ref: https://github.com/liady/ChatGPT-pdf/blob/main/src/content_script.js
|
||||
// @ref: https://github.com/liady/ChatGPT-pdf
|
||||
|
||||
async function init() {
|
||||
const chatConf = await invoke('get_chat_conf') || {};
|
||||
if (window.buttonsInterval) {
|
||||
clearInterval(window.buttonsInterval);
|
||||
}
|
||||
@@ -16,7 +17,7 @@ async function init() {
|
||||
});
|
||||
if (hasTryAgainButton && buttons.length === 1) {
|
||||
const TryAgainButton = actionsArea.querySelector("button");
|
||||
addActionsButtons(actionsArea, TryAgainButton);
|
||||
addActionsButtons(actionsArea, TryAgainButton, chatConf);
|
||||
} else if (!hasTryAgainButton) {
|
||||
removeButtons();
|
||||
}
|
||||
@@ -28,7 +29,7 @@ const Format = {
|
||||
PDF: "pdf",
|
||||
};
|
||||
|
||||
function addActionsButtons(actionsArea, TryAgainButton) {
|
||||
function addActionsButtons(actionsArea, TryAgainButton, chatConf) {
|
||||
const downloadButton = TryAgainButton.cloneNode(true);
|
||||
downloadButton.id = "download-png-button";
|
||||
downloadButton.innerText = "Generate PNG";
|
||||
@@ -36,6 +37,7 @@ function addActionsButtons(actionsArea, TryAgainButton) {
|
||||
downloadThread();
|
||||
};
|
||||
actionsArea.appendChild(downloadButton);
|
||||
|
||||
const downloadPdfButton = TryAgainButton.cloneNode(true);
|
||||
downloadPdfButton.id = "download-pdf-button";
|
||||
downloadPdfButton.innerText = "Download PDF";
|
||||
@@ -43,13 +45,16 @@ function addActionsButtons(actionsArea, TryAgainButton) {
|
||||
downloadThread({ as: Format.PDF });
|
||||
};
|
||||
actionsArea.appendChild(downloadPdfButton);
|
||||
const exportHtml = TryAgainButton.cloneNode(true);
|
||||
exportHtml.id = "download-html-button";
|
||||
exportHtml.innerText = "Share Link";
|
||||
exportHtml.onclick = () => {
|
||||
sendRequest();
|
||||
};
|
||||
actionsArea.appendChild(exportHtml);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
function removeButtons() {
|
||||
@@ -73,22 +78,9 @@ function downloadThread({ as = Format.PNG } = {}) {
|
||||
const pixelRatio = window.devicePixelRatio;
|
||||
const minRatio = as === Format.PDF ? 2 : 2.5;
|
||||
window.devicePixelRatio = Math.max(pixelRatio, minRatio);
|
||||
|
||||
html2canvas(elements.thread, {
|
||||
letterRendering: true,
|
||||
onclone: function (cloneDoc) {
|
||||
//Make small fix of position to all the text containers
|
||||
let listOfTexts = cloneDoc.getElementsByClassName("min-h-[20px]");
|
||||
Array.from(listOfTexts).forEach((text) => {
|
||||
text.style.position = "relative";
|
||||
text.style.top = "-8px";
|
||||
});
|
||||
|
||||
//Delete copy button from code blocks
|
||||
let listOfCopyBtns = cloneDoc.querySelectorAll("button.flex");
|
||||
Array.from(listOfCopyBtns).forEach(
|
||||
(btn) => (btn.style.visibility = "hidden")
|
||||
);
|
||||
},
|
||||
}).then(async function (canvas) {
|
||||
elements.restoreLocation();
|
||||
window.devicePixelRatio = pixelRatio;
|
||||
@@ -161,6 +153,8 @@ class Elements {
|
||||
img.setAttribute("srcset_old", srcset);
|
||||
img.setAttribute("srcset", "");
|
||||
});
|
||||
//Fix to the text shifting down when generating the canvas
|
||||
document.body.style.lineHeight = "0.5";
|
||||
}
|
||||
restoreLocation() {
|
||||
this.hiddens.forEach((el) => {
|
||||
@@ -177,6 +171,7 @@ class Elements {
|
||||
img.setAttribute("srcset", srcset);
|
||||
img.setAttribute("srcset_old", "");
|
||||
});
|
||||
document.body.style.lineHeight = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,19 +1,43 @@
|
||||
use crate::utils::{chat_root, create_file, exists};
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Mutex;
|
||||
use anyhow::Result;
|
||||
use serde_json::Value;
|
||||
use std::{collections::BTreeMap, fs, path::PathBuf, sync::Mutex};
|
||||
use tauri::{Manager, 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";
|
||||
|
||||
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";
|
||||
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_window": "",
|
||||
"ua_tray": ""
|
||||
}"#;
|
||||
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_window": "",
|
||||
"ua_tray": ""
|
||||
}"#;
|
||||
|
||||
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),
|
||||
}
|
||||
@@ -22,7 +46,13 @@ 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_window: String,
|
||||
pub ua_tray: String,
|
||||
}
|
||||
|
||||
impl ChatConfJson {
|
||||
@@ -30,10 +60,30 @@ impl ChatConfJson {
|
||||
/// path: ~/.chatgpt/chat.conf.json
|
||||
pub fn init() -> PathBuf {
|
||||
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, r#"{"always_on_top": false}"#).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();
|
||||
}
|
||||
};
|
||||
|
||||
conf_file
|
||||
}
|
||||
|
||||
@@ -42,26 +92,77 @@ impl ChatConfJson {
|
||||
}
|
||||
|
||||
pub fn get_chat_conf() -> Self {
|
||||
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())
|
||||
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 update_chat_conf(always_on_top: bool) {
|
||||
let mut conf = ChatConfJson::get_chat_conf();
|
||||
conf.always_on_top = always_on_top;
|
||||
// https://users.rust-lang.org/t/updating-object-fields-given-dynamic-json/39049/3
|
||||
pub fn amend(new_rules: &Value, app: Option<tauri::AppHandle>) -> 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);
|
||||
}
|
||||
|
||||
fs::write(
|
||||
ChatConfJson::conf_path(),
|
||||
serde_json::to_string(&conf).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
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(())
|
||||
}
|
||||
|
||||
pub fn chat_conf_default() -> Self {
|
||||
serde_json::from_value(serde_json::json!({
|
||||
"always_on_top": false,
|
||||
}))
|
||||
.unwrap()
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,35 +8,43 @@ mod conf;
|
||||
mod utils;
|
||||
|
||||
use app::{cmd, menu, setup};
|
||||
use conf::ChatConfJson;
|
||||
use conf::{ChatConfJson, ChatState};
|
||||
|
||||
fn main() {
|
||||
ChatConfJson::init();
|
||||
let context = tauri::generate_context!();
|
||||
let chat_conf = ChatConfJson::get_chat_conf();
|
||||
let chat_conf2 = chat_conf.clone();
|
||||
let context = tauri::generate_context!();
|
||||
|
||||
tauri::Builder::default()
|
||||
.manage(conf::ChatState::default(&chat_conf))
|
||||
.manage(ChatState::default(chat_conf))
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
cmd::drag_window,
|
||||
cmd::fullscreen,
|
||||
cmd::download,
|
||||
cmd::open_link
|
||||
cmd::open_link,
|
||||
cmd::get_chat_conf,
|
||||
cmd::form_cancel,
|
||||
cmd::form_confirm,
|
||||
cmd::form_msg,
|
||||
])
|
||||
.setup(|app| setup::init(app, chat_conf2))
|
||||
.setup(setup::init)
|
||||
.plugin(tauri_plugin_positioner::init())
|
||||
.menu(menu::init(&chat_conf, &context))
|
||||
.menu(menu::init(&context))
|
||||
.system_tray(menu::tray_menu())
|
||||
.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() {
|
||||
// TODO: https://github.com/tauri-apps/tauri/issues/3084
|
||||
// event.window().hide().unwrap();
|
||||
// https://github.com/tauri-apps/tao/pull/517
|
||||
event.window().minimize().unwrap();
|
||||
let win = event.window();
|
||||
if win.label() == "main" {
|
||||
win.hide().unwrap();
|
||||
} else {
|
||||
// TODO: https://github.com/tauri-apps/tauri/issues/3084
|
||||
// event.window().hide().unwrap();
|
||||
// https://github.com/tauri-apps/tao/pull/517
|
||||
event.window().minimize().unwrap();
|
||||
}
|
||||
api.prevent_close();
|
||||
}
|
||||
})
|
||||
|
||||
@@ -4,18 +4,19 @@ use std::{
|
||||
path::{Path, PathBuf},
|
||||
process::Command,
|
||||
};
|
||||
use tauri::utils::config::Config;
|
||||
use tauri::Manager;
|
||||
// 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()
|
||||
@@ -61,3 +62,20 @@ 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());
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
{
|
||||
"build": {
|
||||
"beforeDevCommand": "",
|
||||
"beforeBuildCommand": "",
|
||||
"devPath": "https://chat.openai.com/",
|
||||
"beforeDevCommand": "npm run dev:fe",
|
||||
"beforeBuildCommand": "npm run build:fe",
|
||||
"devPath": "http://localhost:1420",
|
||||
"distDir": "../dist"
|
||||
},
|
||||
"package": {
|
||||
"productName": "ChatGPT",
|
||||
"version": "0.1.7"
|
||||
"version": "0.3.0"
|
||||
},
|
||||
"tauri": {
|
||||
"allowlist": {
|
||||
"all": true
|
||||
},
|
||||
"systemTray": {
|
||||
"iconPath": "icons/icon.png",
|
||||
"iconAsTemplate": false
|
||||
"iconPath": "icons/tray-icon.png",
|
||||
"iconAsTemplate": true
|
||||
},
|
||||
"bundle": {
|
||||
"active": true,
|
||||
@@ -63,6 +63,16 @@
|
||||
"https://lencx.github.io/ChatGPT/install.json"
|
||||
],
|
||||
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEIxMjY4OUI5MTVFNjBEMDUKUldRRkRlWVZ1WWttc1NGWEE0RFNSb0RqdnhsekRJZTkwK2hVLzhBZTZnaHExSEZ1ZEdzWkpXTHkK"
|
||||
}
|
||||
},
|
||||
"windows": [
|
||||
{
|
||||
"label": "main",
|
||||
"url": "index.html",
|
||||
"title": "ChatGPT",
|
||||
"visible": false,
|
||||
"width": 800,
|
||||
"height": 600
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
24
src/layout/index.scss
vendored
Normal file
24
src/layout/index.scss
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
.chat-logo {
|
||||
text-align: center;
|
||||
padding: 5px 0;
|
||||
|
||||
img {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
}
|
||||
}
|
||||
|
||||
.chat-container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.ant-menu {
|
||||
.ant-menu-item {
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-layout-footer {
|
||||
color: #666 !important;
|
||||
opacity: 0.7;
|
||||
}
|
||||
36
src/layout/index.tsx
vendored
Normal file
36
src/layout/index.tsx
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
import { FC, useState } from 'react';
|
||||
import { Layout, Menu } from 'antd';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import Routes, { menuItems } from '@/routes';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
const { Content, Footer, Sider } = Layout;
|
||||
|
||||
interface ChatLayoutProps {
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
const ChatLayout: FC<ChatLayoutProps> = ({ children }) => {
|
||||
const [collapsed, setCollapsed] = useState(false);
|
||||
const go = useNavigate();
|
||||
|
||||
return (
|
||||
<Layout style={{ minHeight: '100vh' }}>
|
||||
<Sider theme="light" collapsible collapsed={collapsed} onCollapse={(value) => setCollapsed(value)}>
|
||||
<div className="chat-logo"><img src="/logo.png" /></div>
|
||||
<Menu defaultSelectedKeys={['/']} mode="vertical" items={menuItems} onClick={(i) => go(i.key)} />
|
||||
</Sider>
|
||||
<Layout className="chat-layout">
|
||||
<Content className="chat-container">
|
||||
<Routes />
|
||||
</Content>
|
||||
<Footer style={{ textAlign: 'center' }}>
|
||||
<a href="https://github.com/lencx/chatgpt" target="_blank">ChatGPT Desktop Application</a> ©2022 Created by lencx</Footer>
|
||||
</Layout>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChatLayout;
|
||||
20
src/main.scss
vendored
Normal file
20
src/main.scss
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
:root {
|
||||
font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
font-weight: 400;
|
||||
|
||||
color: #2a2a2a;
|
||||
background-color: #f6f6f6;
|
||||
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
html, body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
16
src/main.tsx
vendored
Normal file
16
src/main.tsx
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
import { StrictMode, Suspense } from 'react';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
|
||||
import Layout from '@/layout';
|
||||
import './main.scss';
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
|
||||
<StrictMode>
|
||||
<Suspense fallback={null}>
|
||||
<BrowserRouter>
|
||||
<Layout/>
|
||||
</BrowserRouter>
|
||||
</Suspense>
|
||||
</StrictMode>
|
||||
);
|
||||
44
src/routes.tsx
vendored
Normal file
44
src/routes.tsx
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
import { useRoutes } from 'react-router-dom';
|
||||
import {
|
||||
DesktopOutlined,
|
||||
BulbOutlined
|
||||
} from '@ant-design/icons';
|
||||
import type { RouteObject } from 'react-router-dom';
|
||||
import type { MenuProps } from 'antd';
|
||||
|
||||
import General from '@view/General';
|
||||
import ChatGPTPrompts from '@view/ChatGPTPrompts';
|
||||
|
||||
export type ChatRouteObject = {
|
||||
label: string;
|
||||
icon?: React.ReactNode,
|
||||
};
|
||||
|
||||
export const routes: Array<RouteObject & { meta: ChatRouteObject }> = [
|
||||
{
|
||||
path: '/',
|
||||
element: <General />,
|
||||
meta: {
|
||||
label: 'General',
|
||||
icon: <DesktopOutlined />,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/chatgpt-prompts',
|
||||
element: <ChatGPTPrompts />,
|
||||
meta: {
|
||||
label: 'ChatGPT Prompts',
|
||||
icon: <BulbOutlined />,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
type MenuItem = Required<MenuProps>['items'][number];
|
||||
export const menuItems: MenuItem[] = routes.map(i => ({
|
||||
...i.meta,
|
||||
key: i.path || '',
|
||||
}));
|
||||
|
||||
export default () => {
|
||||
return useRoutes(routes);
|
||||
};
|
||||
7
src/view/ChatGPTPrompts.tsx
vendored
Normal file
7
src/view/ChatGPTPrompts.tsx
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
export default function Dashboard() {
|
||||
return (
|
||||
<div>
|
||||
TODO: ChatGPT Prompts
|
||||
</div>
|
||||
)
|
||||
}
|
||||
102
src/view/General.tsx
vendored
Normal file
102
src/view/General.tsx
vendored
Normal file
@@ -0,0 +1,102 @@
|
||||
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';
|
||||
|
||||
const OriginLabel = ({ url }: { url: string }) => {
|
||||
return (
|
||||
<span>
|
||||
Switch Origin <Tooltip title={`Default: ${url}`}><QuestionCircleOutlined /></Tooltip>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
const disableAuto = {
|
||||
autoCapitalize: 'off',
|
||||
autoComplete: 'off',
|
||||
spellCheck: false
|
||||
}
|
||||
|
||||
export default function General() {
|
||||
const [form] = Form.useForm();
|
||||
const [platformInfo, setPlatform] = useState<string>('');
|
||||
const [chatConf, setChatConf] = useState<any>(null);
|
||||
|
||||
const init = async () => {
|
||||
setPlatform(await platform());
|
||||
const chatData = await invoke('get_chat_conf');
|
||||
setChatConf(chatData);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
init();
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
form.setFieldsValue(clone(chatConf));
|
||||
}, [chatConf])
|
||||
|
||||
const onCancel = () => {
|
||||
form.setFieldsValue(chatConf);
|
||||
};
|
||||
|
||||
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 (
|
||||
<Form
|
||||
form={form}
|
||||
style={{ maxWidth: 500 }}
|
||||
onFinish={onFinish}
|
||||
labelCol={{ span: 8 }}
|
||||
wrapperCol={{ span: 15, offset: 1 }}
|
||||
>
|
||||
<Form.Item label="Theme" name="theme">
|
||||
<Radio.Group>
|
||||
<Radio value="Light">Light</Radio>
|
||||
<Radio value="Dark">Dark</Radio>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
<Form.Item label="Always on Top" name="always_on_top" valuePropName="checked">
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
{platformInfo === 'darwin' && (
|
||||
<Form.Item label="Titlebar" name="titlebar" valuePropName="checked">
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
)}
|
||||
<Form.Item label={<OriginLabel url={chatConf?.default_origin} />} name="origin">
|
||||
<Input placeholder="https://chat.openai.com" {...disableAuto} />
|
||||
</Form.Item>
|
||||
<Form.Item label="User Agent (Window)" name="ua_window">
|
||||
<Input.TextArea autoSize={{ minRows: 4, maxRows: 4 }} {...disableAuto} placeholder="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36" />
|
||||
</Form.Item>
|
||||
<Form.Item label="User Agent (SystemTray)" name="ua_tray">
|
||||
<Input.TextArea autoSize={{ minRows: 4, maxRows: 4 }} {...disableAuto} placeholder="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36" />
|
||||
</Form.Item>
|
||||
<Form.Item>
|
||||
<Space size={20}>
|
||||
<Button onClick={onCancel}>Cancel</Button>
|
||||
<Button type="primary" htmlType="submit">
|
||||
Submit
|
||||
</Button>
|
||||
</Space>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
1
src/vite-env.d.ts
vendored
Normal file
1
src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
28
tsconfig.json
Normal file
28
tsconfig.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["DOM", "DOM.Iterable", "ESNext"],
|
||||
"allowJs": false,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": false,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["src/*"],
|
||||
"@view/*": ["src/view/*"],
|
||||
"@comps/*": ["src/components/*"],
|
||||
"@layout/*": ["src/layout/*"],
|
||||
}
|
||||
},
|
||||
"include": ["src"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
}
|
||||
9
tsconfig.node.json
Normal file
9
tsconfig.node.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Node",
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
28
vite.config.ts
Normal file
28
vite.config.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { defineConfig } from "vite";
|
||||
import react from "@vitejs/plugin-react";
|
||||
import tsconfigPaths from "vite-tsconfig-paths";
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [tsconfigPaths(), react()],
|
||||
|
||||
// Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
|
||||
// prevent vite from obscuring rust errors
|
||||
clearScreen: false,
|
||||
// tauri expects a fixed port, fail if that port is not available
|
||||
server: {
|
||||
port: 1420,
|
||||
strictPort: true,
|
||||
},
|
||||
// to make use of `TAURI_DEBUG` and other env variables
|
||||
// https://tauri.studio/v1/api/config#buildconfig.beforedevcommand
|
||||
envPrefix: ["VITE_", "TAURI_"],
|
||||
build: {
|
||||
// Tauri supports es2021
|
||||
target: ["es2021", "chrome100", "safari13"],
|
||||
// don't minify for debug builds
|
||||
minify: !process.env.TAURI_DEBUG ? "esbuild" : false,
|
||||
// produce sourcemaps for debug builds
|
||||
sourcemap: !!process.env.TAURI_DEBUG,
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user