Compare commits

..

No commits in common. "main" and "v1.0.0" have entirely different histories.
main ... v1.0.0

15 changed files with 257 additions and 306 deletions

View File

@ -6,17 +6,7 @@ labels:
body:
- type: markdown
attributes:
value: |
Please **DO NOT** file issues here if you are having problems with ChatGPT itself. Issues should **ONLY** be created here for bugs that pertain to this package, [lencx/ChatGPT](https://github.com/lencx/ChatGPT). If you are experiencing an issue on [chat.openai.com](https://chat.openai.com), please contact **OpenAI** for support or submit feedback through ChatGPT itself. If you **only have an issue with this app** and do not have the issue on [chat.openai.com](https://chat.openai.com), please file an issue here.
Please make sure to [search for existing issues](https://github.com/lencx/ChatGPT/issues) before filing a new one!
- type: checkboxes
attributes:
label: Non-ChatGPT bug
options:
- label: |
This issue does not occur on chat.openai.com and only occurs on this app.
required: true
value: 'Please make sure to [search for existing issues](https://github.com/lencx/ChatGPT/issues) before filing a new one!'
- type: markdown
attributes:
value: |

3
.gitignore vendored
View File

@ -4,9 +4,6 @@ node_modules/
.yarn/*
.pnp.*
# Testing
private/
# rust
target/

View File

@ -32,7 +32,7 @@
### Windows
- [ChatGPT_1.1.0_windows_x86_64.msi](https://github.com/lencx/ChatGPT/releases/download/v1.1.0/ChatGPT_1.1.0_windows_x86_64.msi)
- [ChatGPT_1.0.0_windows_x86_64.msi](https://github.com/lencx/ChatGPT/releases/download/v1.0.0/ChatGPT_1.0.0_windows_x86_64.msi)
- 使用 [winget](https://winstall.app/apps/lencx.ChatGPT):
```bash
@ -47,8 +47,8 @@
### Mac
- [ChatGPT_1.1.0_macos_aarch64.dmg](https://github.com/lencx/ChatGPT/releases/download/v1.1.0/ChatGPT_1.1.0_macos_aarch64.dmg)
- [ChatGPT_1.1.0_macos_x86_64.dmg](https://github.com/lencx/ChatGPT/releases/download/v1.1.0/ChatGPT_1.1.0_macos_x86_64.dmg)
- [ChatGPT_1.0.0_macos_aarch64.dmg](https://github.com/lencx/ChatGPT/releases/download/v1.0.0/ChatGPT_1.0.0_macos_aarch64.dmg)
- [ChatGPT_1.0.0_macos_x86_64.dmg](https://github.com/lencx/ChatGPT/releases/download/v1.0.0/ChatGPT_1.0.0_macos_x86_64.dmg)
- Homebrew \
_[Homebrew 快捷安装](https://brew.sh) ([Cask](https://docs.brew.sh/Cask-Cookbook)):_
```sh
@ -70,8 +70,8 @@ sudo xattr -r -d com.apple.quarantine /YOUR_PATH/ChatGPT.app
### Linux
- [ChatGPT_1.1.0_linux_x86_64.deb](https://github.com/lencx/ChatGPT/releases/download/v1.1.0/ChatGPT_1.1.0_linux_x86_64.deb)
- [ChatGPT_1.1.0_linux_x86_64.AppImage.tar.gz](https://github.com/lencx/ChatGPT/releases/download/v1.1.0/ChatGPT_1.1.0_linux_x86_64.AppImage.tar.gz): **工作可靠,`.deb` 运行失败时可以尝试它**
- [ChatGPT_1.0.0_linux_x86_64.deb](https://github.com/lencx/ChatGPT/releases/download/v1.0.0/ChatGPT_1.0.0_linux_x86_64.deb)
- [ChatGPT_1.0.0_linux_x86_64.AppImage.tar.gz](https://github.com/lencx/ChatGPT/releases/download/v1.0.0/ChatGPT_1.0.0_linux_x86_64.AppImage.tar.gz): **工作可靠,`.deb` 运行失败时可以尝试它**
<!-- tr-download-end -->

View File

@ -1,35 +1,28 @@
<p align="center">
<img width="180" src="./public/logo.png" alt="ChatGPT">
<h1 align="center">ChatGPT</h1>
<p align="center">ChatGPT Desktop Application (Available on Mac, Windows, and Linux)</p>
<p align="center">ChatGPT Desktop Application (Mac, Windows and Linux)</p>
</p>
[![English badge](https://img.shields.io/badge/%E8%8B%B1%E6%96%87-English-blue)](./README.md)
[![简体中文 badge](https://img.shields.io/badge/%E7%AE%80%E4%BD%93%E4%B8%AD%E6%96%87-Simplified%20Chinese-blue)](./README-ZH_CN.md)\
[![ChatGPT downloads](https://img.shields.io/github/downloads/lencx/ChatGPT/total.svg?style=flat-square)](https://github.com/lencx/ChatGPT/releases)
[![chat](https://img.shields.io/badge/chat-discord-blue?style=flat&logo=discord)](https://discord.gg/aPhCRf4zZr)
[![twitter](https://img.shields.io/badge/follow-lencx__-blue?style=flat&logo=Twitter)](https://twitter.com/lencx_)
[![youtube](https://img.shields.io/youtube/channel/subscribers/UC__gTZL-OZKDPic7s_6Ntgg?style=social)](https://www.youtube.com/@lencx)
[![lencx](https://img.shields.io/badge/follow-lencx__-blue?style=flat&logo=Twitter)](https://twitter.com/lencx_)
<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: 40px !important;width: 145px !important;" ></a>
---
> [!NOTE]
> **If you want to experience a more powerful AI wrapper application, you can try the Noi (https://github.com/lencx/Noi), which is a successor to the ChatGPT desktop application concept.**
**It is an unofficial project intended for personal learning and research purposes only. During the time that the ChatGPT desktop application was open-sourced, it received a lot of attention, and I would like to thank everyone for their support. However, as things have developed, there are two issues that seriously affect the project's next development plan:**
- **Some people have used it for repackaging and selling for profit.**
- **The name and icon of ChatGPT may be involved in infringement issues.**
**New repository: https://github.com/lencx/nofwl**
---
This is an unofficial project solely intended for personal learning and research. Since the ChatGPT desktop application was open-sourced, it has garnered a lot of attention, and I want to thank everyone for their support. However, as the project progressed, two issues have arisen that greatly impact its future development:
- Some individuals have repackaged and sold it for profit.
- The name and icon of ChatGPT could potentially lead to infringement disputes.
## Live Demo
- [ChatGPT Desktop Application v1.0.0](https://youtu.be/IIuuB5vFFAQ)
- [ChatGPT automatically performs the "Continue generating" button, freeing up your hands.](https://youtu.be/bbL5cPmiGig)
## 📦 Install
- [📝 Update Log](./UPDATE_LOG.md)
@ -39,7 +32,7 @@ This is an unofficial project solely intended for personal learning and research
### Windows
- [ChatGPT_1.1.0_windows_x86_64.msi](https://github.com/lencx/ChatGPT/releases/download/v1.1.0/ChatGPT_1.1.0_windows_x86_64.msi): Direct download installer
- [ChatGPT_1.0.0_windows_x86_64.msi](https://github.com/lencx/ChatGPT/releases/download/v1.0.0/ChatGPT_1.0.0_windows_x86_64.msi): Direct download installer
- Use [winget](https://winstall.app/apps/lencx.ChatGPT):
```bash
@ -47,15 +40,15 @@ This is an unofficial project solely intended for personal learning and research
winget install --id=lencx.ChatGPT -e
# install the specified version
winget install --id=lencx.ChatGPT -e --version 1.1.0
winget install --id=lencx.ChatGPT -e --version 0.10.0
```
**Note: If the installation path and application name are the same, it will lead to conflict ([#142](https://github.com/lencx/ChatGPT/issues/142))**
### Mac
- [ChatGPT_1.1.0_macos_aarch64.dmg](https://github.com/lencx/ChatGPT/releases/download/v1.1.0/ChatGPT_1.1.0_macos_aarch64.dmg): Direct download installer
- [ChatGPT_1.1.0_macos_x86_64.dmg](https://github.com/lencx/ChatGPT/releases/download/v1.1.0/ChatGPT_1.1.0_macos_x86_64.dmg): Direct download installer
- [ChatGPT_1.0.0_macos_aarch64.dmg](https://github.com/lencx/ChatGPT/releases/download/v1.0.0/ChatGPT_1.0.0_macos_aarch64.dmg): Direct download installer
- [ChatGPT_1.0.0_macos_x86_64.dmg](https://github.com/lencx/ChatGPT/releases/download/v1.0.0/ChatGPT_1.0.0_macos_x86_64.dmg): Direct download installer
- Homebrew \
Or you can install with _[Homebrew](https://brew.sh) ([Cask](https://docs.brew.sh/Cask-Cookbook)):_
```sh
@ -77,8 +70,8 @@ sudo xattr -r -d com.apple.quarantine /YOUR_PATH/ChatGPT.app
### Linux
- [ChatGPT_1.1.0_linux_x86_64.deb](https://github.com/lencx/ChatGPT/releases/download/v1.1.0/ChatGPT_1.1.0_linux_x86_64.deb): Download `.deb` installer, advantage small size, disadvantage poor compatibility
- [ChatGPT_1.1.0_linux_x86_64.AppImage.tar.gz](https://github.com/lencx/ChatGPT/releases/download/v1.1.0/ChatGPT_1.1.0_linux_x86_64.AppImage.tar.gz): Works reliably, you can try it if `.deb` fails to run
- [ChatGPT_1.0.0_linux_x86_64.deb](https://github.com/lencx/ChatGPT/releases/download/v1.0.0/ChatGPT_1.0.0_linux_x86_64.deb): Download `.deb` installer, advantage small size, disadvantage poor compatibility
- [ChatGPT_1.0.0_linux_x86_64.AppImage.tar.gz](https://github.com/lencx/ChatGPT/releases/download/v1.0.0/ChatGPT_1.0.0_linux_x86_64.AppImage.tar.gz): Works reliably, you can try it if `.deb` fails to run
<!-- tr-download-end -->
@ -101,6 +94,12 @@ You can look at **[awesome-chatgpt-prompts](https://github.com/f/awesome-chatgpt
- Customize global shortcuts ([#108](https://github.com/lencx/ChatGPT/issues/108))
- Pop-up Search ([#122](https://github.com/lencx/ChatGPT/issues/122) mouse selected content, no more than 400 characters): The application is built using Tauri, and due to its security restrictions, some of the action buttons will not work, so we recommend going to your browser.
## ❤️ Sponsors
### **ChatGPT-Powered Email Finding & Outreach at Scale**
[FinalScout](https://finalscout.com/?utm_source=github&utm_medium=lencx&utm_campaign=chatgpt): Extract valid email addresses from LinkedIn & craft tailored emails based on LinkedIn profile with ChatGPT, guaranteeing up to 98% email deliverability. Scale your outreach efforts and connect with potential customers or clients like never before. [Begin automating your email finding and writing process](https://finalscout.com/?utm_source=github&utm_medium=lencx&utm_campaign=chatgpt)
## Thanks
- The core implementation of the share button code was copied from the [@liady](https://github.com/liady) extension with some modifications.
@ -112,15 +111,12 @@ You can look at **[awesome-chatgpt-prompts](https://github.com/f/awesome-chatgpt
## 中国用户
> [!NOTE]
> **如果你喜欢 ChatGPT 桌面应用,也可以关注一下 [lencx/Noi](https://github.com/lencx/Noi),它是一个定制化的 AI 浏览器。这里有两篇使用文档,对 Noi 的理念和插件系统做了详细介绍:**
> - [Noi跨平台定制化浏览器最得力 AI 助手](https://mp.weixin.qq.com/s/dAN7LOw7mH609HdAyEvXfg)
> - [Noi插件介绍](https://mp.weixin.qq.com/s/M6gO6MdK5obCvs2LIBZA3w)
国内用户如果遇到使用问题或者想交流 ChatGPT 技巧,可以关注公众号“浮之静”,发送 “chat” 进群参与讨论。公众号会更新[《Tauri 系列》](https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzIzNjE2NTI3NQ==&action=getalbum&album_id=2593843659863752704)文章,技术思考等等,如果对 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">
<a href="https://t.zsxq.com/0bQikmcVw"><img width="360" src="./assets/zsxq.png"></a>
## License
AGPL-3.0 License

View File

@ -1,5 +1,11 @@
# UPDATE LOG
**🛑 URGENT NOTICE: A hacker has been found to take advantage of the heat of `lencx/ChatGPT` to plant a Trojan horse after the fork project and rebuild the installer. If you have friends around you who are using this desktop application, please remind them not to download unknown links freely. Now the project will remove other installation ways and only provide this download link https://github.com/lencx/ChatGPT/releases**
**🛑 紧急通知:目前发现有黑客利用 `lencx/ChatGPT` 的热度,在 fork 项目后植入木马,重新构建安装程序。如果你身边有朋友正在使用此桌面应用,请提醒 TA 们不要随意下载不明链接。现在项目将删除其他安装途径,仅提供此下载链接 https://github.com/lencx/ChatGPT/releases**
---
**It is an unofficial project intended for personal learning and research purposes only. During the time that the ChatGPT desktop application was open-sourced, it received a lot of attention, and I would like to thank everyone for their support. However, as things have developed, there are two issues that seriously affect the project's next development plan:**
- **Some people have used it for repackaging and selling for profit.**
@ -7,13 +13,6 @@
**New repository: https://github.com/lencx/nofwl**
## v1.1.0
Fix:
- Fixed the issue where the cmd slash command does not work in some cases.
- Moved the export button to the sidebar to prevent layout conflicts.
## v1.0.0
Note: This version modifies some configuration files. It is recommended to backup the `~/.chatgpt` folder in advance to avoid loss of important configurations.

View File

@ -1,9 +1,9 @@
cask "chatgpt" do
version "1.1.0"
version "0.12.0"
arch = Hardware::CPU.arch.to_s
sha256s = {
"x86_64" => "6747e61a507402fa4b36db443c37a79299a4e1d4ba9904298a0b00c3e6a243b6",
"aarch64" => "f870ba135ad990715474cbb038b5b38acb8d08640803e2c79c878e210f4800f6"
"x86_64" => "d7f32d11f86ad8ac073dd451452124324e1c9154c318f15b77b5cd254000a3c4",
"aarch64" => "c4c10eeb4a2c9a885da13047340372f461d411711c20472fc673fbf958bf6378"
}
if arch == "arm64" then arch = "aarch64" end
url "https://github.com/lencx/ChatGPT/releases/download/v#{version}/ChatGPT_#{version}_macos_#{arch}.dmg"

44
scripts/chat.js vendored
View File

@ -1,6 +1,6 @@
/**
* @name chat.js
* @version 0.1.4
* @version 0.1.0
* @url https://github.com/lencx/ChatGPT/tree/main/scripts/chat.js
*/
@ -25,7 +25,6 @@ function chatInit() {
});
document.addEventListener('visibilitychange', focusOnInput);
autoContinue();
}
function observeMutations(mutationsList) {
@ -116,42 +115,11 @@ function chatInit() {
};
}
function autoContinue() {
// Create an instance of the observer
const observer = new MutationObserver((mutationsList) => {
for (let mutation of mutationsList) {
if (mutation.type === 'childList') {
const btn = Array.from(mutation.target.querySelectorAll('button.btn')).find((btn) =>
btn.innerText.includes('Continue generating'),
);
if (btn) {
console.log("Found the button of 'Continue generating'");
setTimeout(() => {
console.log('Clicked it to continue generating after 1 second');
btn.click();
}, 1000);
return;
}
}
}
});
// Wait until the form exists
const interval = setInterval(() => {
if (document.forms[0]) {
// Start observing the dom change of the form
observer.observe(document.forms[0], {
attributes: false,
childList: true,
subtree: true,
});
clearInterval(interval); // Stop checking when the form exists
}
}, 1000);
}
init();
}
document.addEventListener('DOMContentLoaded', chatInit);
if (document.readyState === 'complete' || document.readyState === 'interactive') {
chatInit();
} else {
document.addEventListener('DOMContentLoaded', chatInit);
}

61
scripts/cmd.js vendored
View File

@ -1,6 +1,6 @@
/**
* @name cmd.js
* @version 0.1.2
* @version 0.1.0
* @url https://github.com/lencx/ChatGPT/tree/main/scripts/cmd.js
*/
@ -72,12 +72,24 @@ function cmdInit() {
color: #888;
}
.chatappico {
width: 1rem;
height: 1rem;
width: 20px;
height: 20px;
}
.chatappico.png {
width: 0.8rem;
height: 0.9rem;
.chatappico.pdf, .chatappico.md {
width: 22px;
height: 22px;
}
.chatappico.copy {
width: 16px;
height: 16px;
}
.chatappico.cpok {
width: 16px;
height: 16px;
}
.chatappico.refresh {
width: 22px;
height: 22px;
}
#download-markdown-button,
#download-png-button,
@ -122,32 +134,6 @@ function cmdInit() {
}
});
}
if (mutation.type === 'childList' && mutation.removedNodes.length) {
for (let node of mutation.removedNodes) {
if (node.querySelector('form textarea')) {
initDom();
cmdTip();
(async function () {
async function platform() {
return invoke('platform', {
__tauriModule: 'Os',
message: { cmd: 'platform' },
});
}
if (__TAURI_METADATA__.__currentWindow.label !== 'tray') {
const _platform = await platform();
const chatConf = (await invoke('get_app_conf')) || {};
if (/darwin/.test(_platform) && !chatConf.titlebar) {
const nav = document.body.querySelector('nav');
if (nav) {
nav.style.paddingTop = '25px';
}
}
}
})();
}
}
}
}
}).observe(document.body, {
childList: true,
@ -173,17 +159,8 @@ function cmdInit() {
promptDom.style.bottom = '54px';
}
const convertToSafeHtml = (v) => {
return JSON.stringify(v, null, 2)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
};
const itemDom = (v) =>
`<div class="cmd-item" title="${convertToSafeHtml(v.prompt)}" data-cmd="${
`<div class="cmd-item" title="${v.prompt}" data-cmd="${
v.cmd
}" data-prompt="${encodeURIComponent(v.prompt)}"><b title="${v.cmd}">/${v.cmd}</b><i>${
v.act

24
scripts/core.js vendored
View File

@ -1,6 +1,6 @@
/**
* @name core.js
* @version 0.1.2
* @version 0.1.0
* @url https://github.com/lencx/ChatGPT/tree/main/scripts/core.js
*/
@ -84,13 +84,21 @@ function coreInit() {
document.body.appendChild(topDom);
if (window.location.host === 'chat.openai.com') {
const intervalId = setInterval(function () {
const nav = document.body.querySelector('nav');
if (nav) {
nav.style.paddingTop = '25px';
clearInterval(intervalId);
}
}, 1000);
const nav = document.body.querySelector('nav');
if (nav) {
const currentPaddingTop = parseInt(
window
.getComputedStyle(document.querySelector('nav'), null)
.getPropertyValue('padding-top')
.replace('px', ''),
10,
);
const navStyleDom = document.createElement('style');
navStyleDom.innerHTML = `nav{padding-top:${
currentPaddingTop + topDom.clientHeight
}px !important}`;
document.head.appendChild(navStyleDom);
}
}
topDom.addEventListener('mousedown', () => invoke('drag_window'));

309
scripts/export.js vendored
View File

@ -1,68 +1,180 @@
/**
* @name export.js
* @version 0.1.5
* @version 0.1.0
* @url https://github.com/lencx/ChatGPT/tree/main/scripts/export.js
*/
async function exportInit() {
const SELECTOR = 'main div.group';
const USER_INPUT_SELECTOR = 'div.empty\\:hidden';
if (window.location.pathname === '/auth/login') return;
const buttonOuterHTMLFallback = `<button class="btn flex justify-center gap-2 btn-neutral">Try Again</button>`;
removeButtons();
if (window.buttonsInterval) {
clearInterval(window.buttonsInterval);
}
if (window.innerWidth < 767) return;
const chatConf = (await invoke('get_app_conf')) || {};
window.buttonsInterval = setInterval(() => {
const actionsArea = document.querySelector('form>div>div>div');
const hasBtn = document.querySelector('form>div>div>div button');
if (!actionsArea || !hasBtn) {
return;
}
if (shouldAddButtons(actionsArea)) {
let TryAgainButton = actionsArea.querySelector('button');
if (!TryAgainButton) {
const parentNode = document.createElement('div');
parentNode.innerHTML = buttonOuterHTMLFallback;
TryAgainButton = parentNode.querySelector('button');
}
addActionsButtons(actionsArea, TryAgainButton, chatConf);
} else if (shouldRemoveButtons()) {
removeButtons();
}
}, 1000);
const Format = {
PNG: 'png',
PDF: 'pdf',
};
function processNode(node, replaceInUserInput = false) {
let j = node.cloneNode(true);
if (/dark\:bg-gray-800/.test(node.getAttribute('class'))) {
j.innerHTML = `<blockquote>${node.innerHTML}</blockquote>`;
function shouldRemoveButtons() {
if (document.querySelector('form .text-2xl')) {
return true;
}
return false;
}
function shouldAddButtons(actionsArea) {
// first, check if there's a "Try Again" button and no other buttons
const buttons = actionsArea.querySelectorAll('button');
const hasTryAgainButton = Array.from(buttons).some((button) => {
return !/download-/.test(button.id);
});
const stopBtn = buttons?.[0]?.innerText;
if (/Stop generating/gi.test(stopBtn)) {
return false;
}
if (replaceInUserInput) {
const userInputBlocks = j.querySelectorAll(USER_INPUT_SELECTOR);
userInputBlocks.forEach((block) => {
//For quicker testing use js fiddle: https://jsfiddle.net/xtraeme/x34ao9jp/13/
block.innerHTML = block.innerHTML
.replace(/&nbsp;|\u00A0/g, ' ') //Replace =C2=A0 (nbsp non-breaking space) /w breaking-space
.replace(/\t/g, '&nbsp;&nbsp;&nbsp;&nbsp;') // Replace tab with 4 non-breaking spaces
.replace(/^ +/gm, function (match) {
return '&nbsp;'.repeat(match.length);
}) //Add =C2=A0
.replace(/\n/g, '<br/>');
});
if (
buttons.length === 2 &&
(/Regenerate response/gi.test(stopBtn) || buttons[1].innerText === '')
) {
return true;
}
return j.innerHTML;
if (hasTryAgainButton && buttons.length === 1) {
return true;
}
// otherwise, check if open screen is not visible
const isOpenScreen = document.querySelector('h1.text-4xl');
if (isOpenScreen) {
return false;
}
// check if the conversation is finished and there are no share buttons
const finishedConversation = document.querySelector('form button>svg');
const hasShareButtons = actionsArea.querySelectorAll('button[share-ext]');
if (finishedConversation && !hasShareButtons.length) {
return true;
}
return false;
}
function removeButtons() {
const downloadPngButton = document.getElementById('download-png-button');
const downloadPdfButton = document.getElementById('download-pdf-button');
const downloadMdButton = document.getElementById('download-markdown-button');
const refreshButton = document.getElementById('refresh-page-button');
if (downloadPngButton) {
downloadPngButton.remove();
}
if (downloadPdfButton) {
downloadPdfButton.remove();
}
if (downloadPdfButton) {
downloadMdButton.remove();
}
if (refreshButton) {
refreshButton.remove();
}
}
function addActionsButtons(actionsArea, TryAgainButton) {
// Export markdown
const exportMd = TryAgainButton.cloneNode(true);
exportMd.id = 'download-markdown-button';
exportMd.setAttribute('share-ext', 'true');
exportMd.title = 'Export Markdown';
exportMd.innerHTML = setIcon('md');
exportMd.onclick = () => {
exportMarkdown();
};
actionsArea.appendChild(exportMd);
// Generate PNG
const downloadPngButton = TryAgainButton.cloneNode(true);
downloadPngButton.id = 'download-png-button';
downloadPngButton.setAttribute('share-ext', 'true');
downloadPngButton.title = 'Generate PNG';
downloadPngButton.innerHTML = setIcon('png');
downloadPngButton.onclick = () => {
downloadThread();
};
actionsArea.appendChild(downloadPngButton);
// Generate PDF
const downloadPdfButton = TryAgainButton.cloneNode(true);
downloadPdfButton.id = 'download-pdf-button';
downloadPdfButton.setAttribute('share-ext', 'true');
downloadPdfButton.title = 'Download PDF';
downloadPdfButton.innerHTML = setIcon('pdf');
downloadPdfButton.onclick = () => {
downloadThread({ as: Format.PDF });
};
actionsArea.appendChild(downloadPdfButton);
// Refresh
const refreshButton = TryAgainButton.cloneNode(true);
refreshButton.id = 'refresh-page-button';
refreshButton.title = 'Refresh the Page';
refreshButton.innerHTML = setIcon('refresh');
refreshButton.onclick = () => {
window.location.reload();
};
actionsArea.appendChild(refreshButton);
}
async function exportMarkdown() {
const allBlocks = document.querySelectorAll(SELECTOR);
const nodes = Array.from(allBlocks);
//<code> blocks with leading spaces mess up ExportMD markdown conversion. ex/
// <code ...> import package
//becomes (spaces are moved to the front of the ''' line)):
// '''Python import package
//so we remove whitespace after <code> tags and add <br/> and/or \n
allBlocks.forEach((block) => {
block.innerHTML = block.innerHTML.replace(/(<code[^>]*>)\s*/g, '$1<br\\/>\n'); // Add \n or <br/> after opening code tag
});
const updatedContent = nodes.map((i) => processNode(i, true)).join('');
const data = ExportMD.turndown(updatedContent);
const content = Array.from(document.querySelectorAll('main div.group'))
.map((i) => {
let j = i.cloneNode(true);
if (/dark\:bg-gray-800/.test(i.getAttribute('class'))) {
j.innerHTML = `<blockquote>${i.innerHTML}</blockquote>`;
}
return j.innerHTML;
})
.join('');
const data = ExportMD.turndown(content);
const { id, filename } = getName();
await invoke('save_file', { name: `notes/${id}.md`, content: data });
await invoke('download_list', { pathname: 'chat.notes.json', filename, id, dir: 'notes' });
}
async function downloadThread({ as = Format.PNG } = {}) {
async function downloadThread({ asF = Format.PNG } = {}) {
const { startLoading, stopLoading } = new window.__LoadingMask('Exporting in progress...');
startLoading();
const elements = new Elements();
await elements.fixLocation();
const pixelRatio = window.devicePixelRatio;
const minRatio = as === Format.PDF ? 2 : 2.5;
const minRatio = asF === Format.PDF ? 2 : 2.5;
window.devicePixelRatio = Math.max(pixelRatio, minRatio);
html2canvas(elements.thread, {
@ -73,7 +185,7 @@ async function exportInit() {
window.devicePixelRatio = pixelRatio;
const imgData = canvas.toDataURL('image/png');
requestAnimationFrame(async () => {
if (as === Format.PDF) {
if (asF === Format.PDF) {
await handlePdf(imgData, canvas, pixelRatio);
} else {
await handleImg(imgData);
@ -155,13 +267,11 @@ async function exportInit() {
const chatImagePromises = this.chatImages.map(async (img) => {
const src = img.getAttribute('src');
if (!/^http/.test(src)) return;
if (['fileserviceuploadsperm.blob.core.windows.net'].includes(new URL(src)?.host)) return;
const data = await invoke('fetch_image', { url: src });
const blob = new Blob([new Uint8Array(data)], { type: 'image/png' });
img.src = URL.createObjectURL(blob);
});
await Promise.all(chatImagePromises);
document.body.style.lineHeight = '0.5';
}
async restoreLocation() {
this.hiddens.forEach((el) => {
@ -176,6 +286,15 @@ async function exportInit() {
}
}
function setIcon(type) {
return {
png: `<svg class="chatappico" viewBox="0 0 1070 1024"><path d="M981.783273 0H85.224727C38.353455 0 0 35.374545 0 83.083636v844.893091c0 47.616 38.353455 86.574545 85.178182 86.574546h903.633454c46.917818 0 81.733818-38.958545 81.733819-86.574546V83.083636C1070.592 35.374545 1028.701091 0 981.783273 0zM335.825455 135.912727c74.193455 0 134.330182 60.974545 134.330181 136.285091 0 75.170909-60.136727 136.192-134.330181 136.192-74.286545 0-134.516364-61.021091-134.516364-136.192 0-75.264 60.229818-136.285091 134.516364-136.285091z m-161.512728 745.937455a41.890909 41.890909 0 0 1-27.648-10.379637 43.752727 43.752727 0 0 1-4.654545-61.067636l198.097454-255.162182a42.123636 42.123636 0 0 1 57.716364-6.702545l116.549818 128.139636 286.906182-352.814545c14.615273-18.711273 90.251636-106.775273 135.866182-6.935273 0.093091-0.093091 0.093091 112.965818 0.232727 247.761455 0.093091 140.8 0.093091 317.067636 0.093091 317.067636-1.024-0.093091-762.740364 0.093091-763.112727 0.093091z" fill="currentColor"></path></svg>`,
pdf: `<svg class="chatappico pdf" viewBox="0 0 1024 1024"><path d="M821.457602 118.382249H205.725895c-48.378584 0-87.959995 39.583368-87.959996 87.963909v615.731707c0 48.378584 39.581411 87.959995 87.959996 87.959996h615.733664c48.380541 0 87.961952-39.581411 87.961952-87.959996V206.346158c-0.001957-48.378584-39.583368-87.963909-87.963909-87.963909zM493.962468 457.544987c-10.112054 32.545237-21.72487 82.872662-38.806571 124.248336-8.806957 22.378397-8.380404 18.480717-15.001764 32.609808l5.71738-1.851007c58.760658-16.443827 99.901532-20.519564 138.162194-27.561607-7.67796-6.06371-14.350194-10.751884-19.631237-15.586807-26.287817-29.101504-35.464584-34.570387-70.440002-111.862636v0.003913z m288.36767 186.413594c-7.476424 8.356924-20.670227 13.191847-40.019704 13.191847-33.427694 0-63.808858-9.229597-107.79277-31.660824-75.648648 8.356924-156.097 17.214754-201.399704 31.729308-2.199293 0.876587-4.832967 1.759043-7.916674 3.077836-54.536215 93.237125-95.031389 132.767663-130.621199 131.19646-11.286054-0.49895-27.694661-7.044-32.973748-10.11988l-6.52157-6.196764-2.29517-4.353583c-3.07588-7.91863-3.954423-15.395054-2.197337-23.751977 4.838837-23.309771 29.907651-60.251638 82.686779-93.237126 8.356924-6.159587 27.430511-15.897917 45.020944-24.25484 13.311204-21.177004 19.45905-34.744531 36.341171-72.259702 19.102937-45.324228 36.505531-99.492589 47.500041-138.191543v-0.44025c-16.267727-53.219378-25.945401-89.310095-9.67376-147.80856 3.958337-16.71189 18.46702-33.864031 34.748444-33.864031h10.552304c10.115967 0 19.791684 3.520043 26.829814 10.552304 29.029107 29.031064 15.39114 103.824649 0.8805 162.323113-0.8805 2.63563-1.322707 4.832967-1.761 6.153717 17.59239 49.697378 45.400538 98.774492 73.108895 121.647926 11.436717 8.791304 22.638634 18.899444 36.71098 26.814161 19.791684-2.20125 37.517128-4.11487 55.547812-4.11487 54.540128 0 87.525615 9.67963 100.279169 30.351814 4.400543 7.034217 6.595923 15.389184 5.281043 24.1844-0.44025 10.996467-4.39663 21.112434-12.31526 29.031064z m-27.796407-36.748157c-4.394673-4.398587-17.024957-16.936907-78.601259-16.936907-3.073923 0-10.622744-0.784623-14.57521 3.612007 32.104987 14.072347 62.830525 24.757704 83.058545 24.757703 3.083707 0 5.72325-0.442207 8.356923-0.876586h1.759044c2.20125-0.8805 3.520043-1.324663 3.960293-5.71738-0.87463-1.324663-1.757087-3.083707-3.958336-4.838837z m-387.124553 63.041845c-9.237424 5.27713-16.71189 10.112054-21.112433 13.634053-31.226444 28.586901-51.018128 57.616008-53.217422 74.331812 19.789727-6.59788 45.737084-35.626987 74.329855-87.961952v-0.003913z m125.574957-297.822284l2.197336-1.761c3.079793-14.072347 5.232127-29.189554 7.87167-38.869184l1.318794-7.036174c4.39663-25.070771 2.71781-39.720334-4.76057-50.272637l-6.59788-2.20125a57.381208 57.381208 0 0 0-3.079794 5.27713c-7.474467 18.47289-7.063567 55.283661 3.0524 94.865072l-0.001956-0.001957z" fill="currentColor"></path></svg>`,
md: `<svg class="chatappico md" viewBox="0 0 1024 1024" width="200" height="200"><path d="M128 128h768a42.666667 42.666667 0 0 1 42.666667 42.666667v682.666666a42.666667 42.666667 0 0 1-42.666667 42.666667H128a42.666667 42.666667 0 0 1-42.666667-42.666667V170.666667a42.666667 42.666667 0 0 1 42.666667-42.666667z m170.666667 533.333333v-170.666666l85.333333 85.333333 85.333333-85.333333v170.666666h85.333334v-298.666666h-85.333334l-85.333333 85.333333-85.333333-85.333333H213.333333v298.666666h85.333334z m469.333333-128v-170.666666h-85.333333v170.666666h-85.333334l128 128 128-128h-85.333333z" p-id="1381" fill="currentColor"></path></svg>`,
refresh: `<svg class="chatappico refresh" viewBox="0 0 1024 1024"><path d="M512 63.5C264.3 63.5 63.5 264.3 63.5 512S264.3 960.5 512 960.5 960.5 759.7 960.5 512 759.7 63.5 512 63.5zM198 509.6h87.6c0-136.3 102.3-243.4 233.7-238.5 43.8 0 82.8 14.6 121.7 34.1L597.2 349c-24.4-9.8-53.6-19.5-82.8-19.5-92.5 0-170.4 77.9-170.4 180.1h87.6L314.8 631.3 198 509.6z m540.3-0.1c0 131.4-102.2 243.4-228.8 243.4-43.8 0-82.8-19.4-121.7-38.9l43.8-43.8c24.4 9.8 53.6 19.5 82.8 19.5 92.5 0 170.4-77.9 170.4-180.1h-92.5l116.9-121.7L826 509.5h-87.7z" fill="currentColor"></path></svg>`,
}[type];
}
function formatDateTime() {
const now = new Date();
const year = now.getFullYear();
@ -188,120 +307,16 @@ async function exportInit() {
return formattedDateTime;
}
// function sanitizeFilename(filename) {
// if (!filename || filename === '') return '';
// // Replace whitespaces with underscores
// let sanitizedFilename = filename.replace(/\s/g, '_');
// // Replace invalid filename characters with #
// const invalidCharsRegex = /[<>:"/\\|?*\x00-\x1F]/g;
// sanitizedFilename = sanitizedFilename.replace(invalidCharsRegex, '#');
// // Check for filenames ending with period or space (Windows)
// if (sanitizedFilename && /[\s.]$/.test(sanitizedFilename)) {
// sanitizedFilename = sanitizedFilename.slice(0, -1) + '#';
// }
// //console.log(sanitizedFilename);
// return sanitizedFilename;
// }
function btnInit() {
const intervalId = setInterval(function () {
const navActionArea = document.querySelector('nav .border-t > div');
const addArea = document.querySelector('#chatgpt-nav-action-area');
if (!navActionArea || addArea) return;
const cloneNode = document.createElement('div');
cloneNode.id = 'chatgpt-nav-action-area';
cloneNode.classList = `${navActionArea.className} border-b border-white/20 mb-2 pb-2`;
cloneNode.appendChild(addBtn('png'));
cloneNode.appendChild(addBtn('pdf'));
cloneNode.appendChild(addBtn('md'));
cloneNode.appendChild(addBtn('refresh'));
navActionArea.parentNode.insertBefore(cloneNode, navActionArea);
clearInterval(intervalId);
function debounce(func, wait) {
let timeout;
return function () {
const context = this;
const args = arguments;
clearTimeout(timeout);
timeout = setTimeout(() => {
func.apply(context, args);
}, wait);
};
}
const target = document.querySelector('nav');
const debouncedFunction = debounce(function () {
btnInit();
}, 300);
const observer = new MutationObserver(debouncedFunction);
const config = { attributes: true, childList: true, characterData: true, subtree: true };
observer.observe(target, config);
}, 1000);
}
function addBtn(type) {
const btn = document.createElement('button');
btn.className = `btn dark:text-gray-500 hover:dark:text-gray-300`;
btn.title = {
png: 'Export PNG',
pdf: 'Export PDF',
md: 'Export Markdown',
refresh: 'Refresh the Page',
}[type];
btn.innerHTML = setIcon(type);
btn.onclick = () => {
const content = document.querySelector('main .group');
if (!content && type !== 'refresh') {
alert('Please open a thread first.');
return;
}
switch (type) {
case 'png':
downloadThread();
break;
case 'pdf':
downloadThread({ as: 'pdf' });
break;
case 'md':
exportMarkdown();
break;
case 'refresh':
window.location.reload();
break;
default:
break;
}
};
return btn;
}
function setIcon(type) {
return {
png: `<svg class="chatappico png" viewBox="0 0 1024 1024"><path d="M264.258065 338.580645c16.780387 0 31.281548-6.144 43.536516-18.398968 12.254968-12.221935 18.398968-26.756129 18.398967-43.536516s-6.144-31.281548-18.398967-43.536516A59.524129 59.524129 0 0 0 264.258065 214.709677c-16.780387 0-31.281548 6.144-43.536517 18.398968-12.254968 12.221935-18.398968 26.756129-18.398967 43.536516s6.144 31.281548 18.398967 43.536516c12.221935 12.254968 26.756129 18.398968 43.536517 18.398968zM883.612903 28.903226H140.387097a119.345548 119.345548 0 0 0-87.568516 36.302451A119.345548 119.345548 0 0 0 16.516129 152.774194v743.225806c0 34.188387 12.089806 63.388903 36.302452 87.568516a119.345548 119.345548 0 0 0 87.568516 36.302452h743.225806a119.345548 119.345548 0 0 0 87.568516-36.302452A119.345548 119.345548 0 0 0 1007.483871 896v-743.225806a119.345548 119.345548 0 0 0-36.302452-87.568517A119.345548 119.345548 0 0 0 883.612903 28.903226zM264.258065 152.774194c34.188387 0 63.388903 12.089806 87.568516 36.302451a119.345548 119.345548 0 0 1 36.302451 87.568516 119.345548 119.345548 0 0 1-36.302451 87.568516A119.345548 119.345548 0 0 1 264.258065 400.516129a119.345548 119.345548 0 0 1-87.568517-36.302452A119.345548 119.345548 0 0 1 140.387097 276.645161c0-34.188387 12.089806-63.388903 36.302451-87.568516A119.345548 119.345548 0 0 1 264.258065 152.774194zM140.387097 957.935484c-16.780387 0-31.281548-6.144-43.536516-18.398968a59.524129 59.524129 0 0 1-18.398968-43.536516v-29.035355l245.793032-220.655484L635.870968 957.935484h-495.483871z m805.16129-61.935484c0 16.780387-6.144 31.281548-18.398968 43.536516-12.221935 12.254968-26.756129 18.398968-43.536516 18.398968h-159.677935l-228.385033-231.291871L759.741935 462.451613l185.806452 185.806452v247.741935z" fill="currentColor"></path></svg>`,
pdf: `<svg class="chatappico pdf" viewBox="0 0 1024 1024"><path d="M821.457602 118.382249H205.725895c-48.378584 0-87.959995 39.583368-87.959996 87.963909v615.731707c0 48.378584 39.581411 87.959995 87.959996 87.959996h615.733664c48.380541 0 87.961952-39.581411 87.961952-87.959996V206.346158c-0.001957-48.378584-39.583368-87.963909-87.963909-87.963909zM493.962468 457.544987c-10.112054 32.545237-21.72487 82.872662-38.806571 124.248336-8.806957 22.378397-8.380404 18.480717-15.001764 32.609808l5.71738-1.851007c58.760658-16.443827 99.901532-20.519564 138.162194-27.561607-7.67796-6.06371-14.350194-10.751884-19.631237-15.586807-26.287817-29.101504-35.464584-34.570387-70.440002-111.862636v0.003913z m288.36767 186.413594c-7.476424 8.356924-20.670227 13.191847-40.019704 13.191847-33.427694 0-63.808858-9.229597-107.79277-31.660824-75.648648 8.356924-156.097 17.214754-201.399704 31.729308-2.199293 0.876587-4.832967 1.759043-7.916674 3.077836-54.536215 93.237125-95.031389 132.767663-130.621199 131.19646-11.286054-0.49895-27.694661-7.044-32.973748-10.11988l-6.52157-6.196764-2.29517-4.353583c-3.07588-7.91863-3.954423-15.395054-2.197337-23.751977 4.838837-23.309771 29.907651-60.251638 82.686779-93.237126 8.356924-6.159587 27.430511-15.897917 45.020944-24.25484 13.311204-21.177004 19.45905-34.744531 36.341171-72.259702 19.102937-45.324228 36.505531-99.492589 47.500041-138.191543v-0.44025c-16.267727-53.219378-25.945401-89.310095-9.67376-147.80856 3.958337-16.71189 18.46702-33.864031 34.748444-33.864031h10.552304c10.115967 0 19.791684 3.520043 26.829814 10.552304 29.029107 29.031064 15.39114 103.824649 0.8805 162.323113-0.8805 2.63563-1.322707 4.832967-1.761 6.153717 17.59239 49.697378 45.400538 98.774492 73.108895 121.647926 11.436717 8.791304 22.638634 18.899444 36.71098 26.814161 19.791684-2.20125 37.517128-4.11487 55.547812-4.11487 54.540128 0 87.525615 9.67963 100.279169 30.351814 4.400543 7.034217 6.595923 15.389184 5.281043 24.1844-0.44025 10.996467-4.39663 21.112434-12.31526 29.031064z m-27.796407-36.748157c-4.394673-4.398587-17.024957-16.936907-78.601259-16.936907-3.073923 0-10.622744-0.784623-14.57521 3.612007 32.104987 14.072347 62.830525 24.757704 83.058545 24.757703 3.083707 0 5.72325-0.442207 8.356923-0.876586h1.759044c2.20125-0.8805 3.520043-1.324663 3.960293-5.71738-0.87463-1.324663-1.757087-3.083707-3.958336-4.838837z m-387.124553 63.041845c-9.237424 5.27713-16.71189 10.112054-21.112433 13.634053-31.226444 28.586901-51.018128 57.616008-53.217422 74.331812 19.789727-6.59788 45.737084-35.626987 74.329855-87.961952v-0.003913z m125.574957-297.822284l2.197336-1.761c3.079793-14.072347 5.232127-29.189554 7.87167-38.869184l1.318794-7.036174c4.39663-25.070771 2.71781-39.720334-4.76057-50.272637l-6.59788-2.20125a57.381208 57.381208 0 0 0-3.079794 5.27713c-7.474467 18.47289-7.063567 55.283661 3.0524 94.865072l-0.001956-0.001957z" fill="currentColor"></path></svg>`,
md: `<svg class="chatappico md" viewBox="0 0 1024 1024"><path d="M128 128h768a42.666667 42.666667 0 0 1 42.666667 42.666667v682.666666a42.666667 42.666667 0 0 1-42.666667 42.666667H128a42.666667 42.666667 0 0 1-42.666667-42.666667V170.666667a42.666667 42.666667 0 0 1 42.666667-42.666667z m170.666667 533.333333v-170.666666l85.333333 85.333333 85.333333-85.333333v170.666666h85.333334v-298.666666h-85.333334l-85.333333 85.333333-85.333333-85.333333H213.333333v298.666666h85.333334z m469.333333-128v-170.666666h-85.333333v170.666666h-85.333334l128 128 128-128h-85.333333z" fill="currentColor"></path></svg>`,
refresh: `<svg class="chatappico refresh" viewBox="0 0 1024 1024"><path d="M512 63.5C264.3 63.5 63.5 264.3 63.5 512S264.3 960.5 512 960.5 960.5 759.7 960.5 512 759.7 63.5 512 63.5zM198 509.6h87.6c0-136.3 102.3-243.4 233.7-238.5 43.8 0 82.8 14.6 121.7 34.1L597.2 349c-24.4-9.8-53.6-19.5-82.8-19.5-92.5 0-170.4 77.9-170.4 180.1h87.6L314.8 631.3 198 509.6z m540.3-0.1c0 131.4-102.2 243.4-228.8 243.4-43.8 0-82.8-19.4-121.7-38.9l43.8-43.8c24.4 9.8 53.6 19.5 82.8 19.5 92.5 0 170.4-77.9 170.4-180.1h-92.5l116.9-121.7L826 509.5h-87.7z" fill="currentColor"></path></svg>`,
}[type];
}
function getName() {
const id = window.crypto.getRandomValues(new Uint32Array(1))[0].toString(36);
const name =
document.querySelector('nav .overflow-y-auto a.hover\\:bg-gray-800')?.innerText?.trim() || '';
// clean_name = sanitizeFilename(name);
return {
id,
filename: name ? name : id,
pathname: 'chat.download.json',
};
return { filename: name ? name : id, id, pathname: 'chat.download.json' };
}
btnInit();
}
window.addEventListener('resize', exportInit);
if (document.readyState === 'complete' || document.readyState === 'interactive') {
exportInit();
} else {

View File

@ -5,16 +5,20 @@
"url": "https://github.com/lencx/ChatGPT/tree/main/scripts",
"scripts": [
{
"name": "chat.js",
"version": "0.1.4"
"name": "core.js",
"version": "0.1.0"
},
{
"name": "cmd.js",
"version": "0.1.2"
"version": "0.1.0"
},
{
"name": "chat.js",
"version": "0.1.0"
},
{
"name": "core.js",
"version": "0.1.2"
"version": "0.1.0"
},
{
"name": "dalle2.js",
@ -22,7 +26,7 @@
},
{
"name": "export.js",
"version": "0.1.5"
"version": "0.1.0"
},
{
"name": "markdown.export.js",

View File

@ -26,7 +26,7 @@ dark-light = "1.0.0"
wry = "0.*"
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1.23.0", features = ["macros"] }
tauri = { version = "1.3.0", features = ["devtools", "dialog-all", "fs-create-dir", "fs-exists", "fs-read-dir", "fs-read-file", "fs-remove-dir", "fs-remove-file", "fs-write-file", "global-shortcut", "global-shortcut-all", "os-all", "path-all", "process-all", "shell-all", "shell-open-api", "system-tray", "updater"] }
tauri = { version = "1.3.0", features = ["devtools", "fs-create-dir", "fs-exists", "fs-read-dir", "fs-read-file", "fs-remove-dir", "fs-remove-file", "fs-write-file", "global-shortcut", "global-shortcut-all", "os-all", "path-all", "process-all", "shell-all", "shell-open-api", "system-tray", "updater"] }
tauri-plugin-positioner = { git = "https://github.com/lencx/tauri-plugins-workspace", features = ["system-tray"] }
tauri-plugin-log = { git = "https://github.com/lencx/tauri-plugins-workspace", branch = "dev", features = ["colored"] }
tauri-plugin-autostart = { git = "https://github.com/lencx/tauri-plugins-workspace", branch = "dev" }

View File

@ -102,9 +102,7 @@ pub fn init(app: &mut App) -> Result<(), Box<dyn std::error::Error>> {
.initialization_script(&load_script("chat.js"))
}
if let Err(err) = main_win.build() {
error!("core_build_error: {}", err);
}
main_win.build().unwrap();
});
}

View File

@ -7,7 +7,7 @@
},
"package": {
"productName": "ChatGPT",
"version": "1.1.0"
"version": "1.0.0"
},
"tauri": {
"allowlist": {
@ -38,9 +38,6 @@
"os": {
"all": true
},
"dialog": {
"all": true
},
"process": {
"all": true,
"exit": true,

View File

@ -40,7 +40,8 @@ const AboutChatGPT = () => {
<p>
It is just a wrapper for the
<a href="https://chat.openai.com" target="_blank" title="https://chat.openai.com">
OpenAI ChatGPT
{' '}
OpenAI ChatGPT{' '}
</a>
website, no other data transfer exists (you can check the{' '}
<a
@ -48,7 +49,8 @@ const AboutChatGPT = () => {
target="_blank"
title="https://github.com/lencx/ChatGPT"
>
source code
{' '}
source code{' '}
</a>
). The development and maintenance of this software has taken up a lot of my time. If it
helps you, you can buy me a cup of coffee (Chinese users can use WeChat to scan the code),
@ -60,7 +62,7 @@ const AboutChatGPT = () => {
src="https://cdn.buymeacoffee.com/buttons/v2/default-blue.png"
alt="Buy Me A Coffee"
/>
</a>
</a>{' '}
<br />
<img
width="200"