Compare commits

...

68 Commits

Author SHA1 Message Date
Yifei Zhang
b1e26a5063 Merge pull request #510 from Yidadaa/user-prompt
feat: split docs, clear messages and fix stop response
2023-04-06 04:02:48 +08:00
Yidadaa
c77f946be1 fixup 2023-04-06 04:01:53 +08:00
Yidadaa
acfe6eec18 fix: #463 add subscrption total amount 2023-04-06 03:56:54 +08:00
Yidadaa
dce2546f5f fix: #451 override default model config 2023-04-06 03:26:42 +08:00
Yidadaa
8e560d2b2e fix: #410 can not stop response 2023-04-06 03:19:33 +08:00
Yidadaa
c2b37f811b feat: close #469 support reset session and do not send memory 2023-04-06 02:37:12 +08:00
Yidadaa
0e77177a60 fix: #439 context prompt input with textarea 2023-04-06 02:05:44 +08:00
Yifei Zhang
9aaceda40d Merge pull request #482 from zyqq/main
fix(utils): 修复复制问题
2023-04-06 01:48:20 +08:00
Yifei Zhang
eb1573de4f Update issue templates 2023-04-06 01:44:02 +08:00
Yifei Zhang
dbdb9baf5f Update issue templates 2023-04-06 01:35:53 +08:00
Yidadaa
7da0cc6551 doc: update screenshots 2023-04-06 01:22:29 +08:00
Yidadaa
de78fd07c5 doc: update document 2023-04-06 00:41:44 +08:00
Yifei Zhang
2ae62d9c0e Merge pull request #497 from waltcow/main
fix 头像无法正常显示
2023-04-05 22:07:17 +08:00
Yifei Zhang
ee4564d926 Merge pull request #500 from yorunning/fix
fix: header title overflow
2023-04-05 20:02:26 +08:00
Yorun
909e2ab60f fix: header title overflow 2023-04-05 11:32:45 +00:00
Yifei Zhang
17d6f3d715 Merge pull request #496 from quark-zju/m/memo-markdown
perf: memorize markdown rendering
2023-04-05 18:00:57 +08:00
waltcow
f4fc682fa3 add getEmojiUrl as util function 2023-04-05 15:48:44 +08:00
waltcow
80904ac2bb fix import 2023-04-05 15:31:46 +08:00
waltcow
91322f33eb emoji resouce CDN format override 2023-04-05 15:29:25 +08:00
Jun Wu
962f434e17 perf: memorize markdown rendering
Markdown rendering can take time. Use `React.memo` for better performance.

The improvement is especially visible if there are complex elements. For
example, a `<Chat />` with an output of `如何推导三次方程求根方程?` (which
uses latex) now renders in about 5ms, down from ~140ms.

Related: #302
2023-04-05 00:16:10 -07:00
Yifei Zhang
4542c18385 Merge pull request #491 from quark-zju/patch-1
chore: removed redundant checkUsage
2023-04-05 15:06:49 +08:00
Jun Wu
2625c1246b chore: removed redundant checkUsage
`checkUsage()` was already called by another `useEffect`.
There is no need to call it twice.
2023-04-04 22:41:33 -07:00
yiqiuzheng
0af55366f1 fix(utils): 修复复制问题 2023-04-05 02:49:44 +08:00
Yifei Zhang
bd7567fa85 Merge pull request #434 from gaogao1030/optimize
[opt]  Optimize the display of exported chat history content.
2023-04-05 02:08:38 +08:00
Yifei Zhang
9d328e489b Merge pull request #476 from ClarenceYk/main
Update settings.tsx
2023-04-05 01:21:03 +08:00
MaYuKe
8ba1dedcea Update settings.tsx
Update parameter `step` of historyMessageCount to 1.
2023-04-04 23:32:19 +08:00
Yifei Zhang
bf8d136936 Merge pull request #456 from KakaWanYifan/linkTarget={_blank}
linkTarget={'_blank'},这个对于对话框中的链接,会在新的tab页打开,体验更好。
2023-04-04 17:07:41 +08:00
kakawanyifan
0508b0080f linkTarget={'_blank'},这个对于对话框中的链接,会在新的tab页打开,体验更好。 2023-04-04 16:33:36 +08:00
Yifei Zhang
1d2a95361b Merge pull request #447 from KakaWanYifan/main
访问改成授权
2023-04-04 15:00:43 +08:00
kakawanyifan
47f055872e 访问改成授权 2023-04-04 13:27:50 +08:00
Yifei Zhang
15429cd67f Merge pull request #435 from yorunning/fix-1
fix: add media query to theme-color, fix auto theme not updating theme-color
2023-04-04 11:16:34 +08:00
ガオガオ
be4144fab3 [opt] Optimize the display of exported chat history content. 2023-04-04 05:55:06 +08:00
Yifei Zhang
7d7f3716be Update README.md 2023-04-04 01:06:00 +08:00
yidadaa
4e644cfca7 fix: #418 valid model config 2023-04-04 01:05:33 +08:00
Yorun
30ff915e9d fix: add media query to theme-color, fix auto theme not updating theme-color 2023-04-03 16:24:59 +00:00
Yifei Zhang
7572c99f4d Merge pull request #420 from ZyXianzi/main
feat: optimize usage display
2023-04-03 22:52:08 +08:00
Feifan Zheng
bb30fdfa17 feat: optimize usage display 2023-04-03 12:18:04 +00:00
Yifei Zhang
a4743034a9 Merge pull request #415 from RugerMcCarthy/opt/back_to_autoscroll
opt: back to autoscroll when hit bottom
2023-04-03 17:57:40 +08:00
RugerMc
c2a79e8a11 opt: back to auto scroll 2023-04-03 17:51:35 +08:00
Ruger
ec88785b6d Merge branch 'Yidadaa:main' into main 2023-04-03 16:37:44 +08:00
Yifei Zhang
d923869dc3 Merge pull request #404 from cyhhao/pr2
optimize: scrolling experience
2023-04-03 15:33:01 +08:00
cyhhao
ae8ceb8dca rm input mouseover 2023-04-03 15:21:03 +08:00
cyhhao
8db26bbd5f rm msg body mouseover 2023-04-03 15:20:16 +08:00
cyhhao
efd3310711 fix conflict 2023-04-03 14:58:34 +08:00
cyhhao
a6890c0f58 optimize: scrolling experience 2023-04-03 14:56:13 +08:00
Yifei Zhang
b5a69b66b2 Merge pull request #401 from cyhhao/pr2
fix: context message should exclude all error tips
2023-04-03 14:26:32 +08:00
Yifei Zhang
54447ba677 Merge pull request #400 from yorunning/main
ci(sync): add push permission
2023-04-03 14:24:08 +08:00
GH Action - Upstream Sync
13a86aead7 Merge branch 'main' of https://github.com/Yidadaa/ChatGPT-Next-Web 2023-04-03 06:23:04 +00:00
cyhhao
73f4ea38c6 session message should exclude all error tips 2023-04-03 14:13:57 +08:00
Yorun
82c025a34a ci(sync): add push permission 2023-04-03 06:03:36 +00:00
Yifei Zhang
fb127f7857 Merge pull request #377 from ilario92/main
Update: locales in Italian
2023-04-03 13:44:40 +08:00
Yifei Zhang
6f82140ba5 Update README.md 2023-04-03 13:37:55 +08:00
github-actions[bot]
6a9751efce Merge pull request #3 from Yidadaa/main
Fork Sync
2023-04-03 05:29:44 +00:00
Yidadaa
5c75b6c784 fix: #397 #373 Array.prototype.at polyfill errors 2023-04-03 13:29:37 +08:00
Ruger
34ea20b109 Merge pull request #2 from Yidadaa/main
Fork Sync
2023-04-03 13:28:42 +08:00
Yifei Zhang
6e6d2a3310 Update README.md 2023-04-03 11:43:38 +08:00
Yifei Zhang
56ba8a65e0 fix: improve scroll 2023-04-03 03:35:24 +00:00
Yifei Zhang
0e784c50ad fix: #384 improve scroll 2023-04-03 03:27:42 +00:00
Yifei Zhang
73865651a0 fix: #366 use fallback copy 2023-04-03 03:27:42 +00:00
Yifei Zhang
f863a562a0 Merge pull request #392 from yorunning/main
ci(sync): skip main repo execution
2023-04-03 10:53:01 +08:00
Yifei Zhang
0d3bd42780 Update app.ts 2023-04-03 10:50:29 +08:00
Yorun
62f8675199 ci(sync): skip main repo execution 2023-04-03 02:11:40 +00:00
Yorun
8a1c2f89be ci(sync): shorten the timer interval 2023-04-03 02:10:48 +00:00
Ilario Scandurra
502d22bd20 Merge branch 'Yidadaa:main' into main 2023-04-02 21:14:04 +02:00
ilarioscandurra
f9f29afba9 Update: locales in Italian 2023-04-02 20:32:43 +02:00
Ilario Scandurra
c98517fc48 Merge branch 'main' into main 2023-04-02 20:13:58 +02:00
ilarioscandurra
f606a61e1c Update: locales in Italian 2023-04-02 19:23:50 +02:00
github-actions[bot]
4dbc984351 Merge pull request #1 from Yidadaa/main
Fork Sync
2023-03-31 08:52:24 +00:00
48 changed files with 1128 additions and 429 deletions

43
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,43 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
** Deployment
- [ ] Docker
- [ ] Vercel
- [ ] Server
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
**Additional Logs**
Add any logs about the problem here.

View File

@@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

20
.github/ISSUE_TEMPLATE/功能建议.md vendored Normal file
View File

@@ -0,0 +1,20 @@
---
name: 功能建议
about: 请告诉我们你的灵光一闪
title: "[Feature] "
labels: ''
assignees: ''
---
**这个功能与现有的问题有关吗?**
如果有关,请在此列出链接或者描述问题。
**你想要什么功能或者有什么建议?**
尽管告诉我们。
**有没有可以参考的同类竞品?**
可以给出参考产品的链接或者截图。
**其他信息**
可以说说你的其他考虑。

23
.github/ISSUE_TEMPLATE/反馈问题.md vendored Normal file
View File

@@ -0,0 +1,23 @@
---
name: 反馈问题
about: 请告诉我们你遇到的问题
title: "[Bug] "
labels: ''
assignees: ''
---
**描述问题**
请在此描述你遇到了什么问题。
**如何复现**
请告诉我们你是通过什么操作触发的该问题。
**截图**
请在此提供控制台截图、屏幕截图或者服务端的 log 截图。
**一些必要的信息**
- 系统:[比如 windows 10/ macos 12/ linux / android 11 / ios 16]
- 浏览器: [比如 chrome, safari]
- 版本: [填写设置页面的版本号]
- 部署方式:[比如 vercel、docker 或者服务器部署]

View File

@@ -1,29 +1,33 @@
name: Upstream Sync
permissions:
contents: write
on:
schedule:
- cron: '0 */12 * * *' # every 12 hours
workflow_dispatch: # on button click
- cron: "0 */6 * * *" # every 6 hours
workflow_dispatch:
jobs:
sync_latest_from_upstream:
name: Sync latest commits from upstream repo
runs-on: ubuntu-latest
if: ${{ github.event.repository.fork }}
steps:
# Step 1: run a standard checkout action, provided by github
- name: Checkout target repo
uses: actions/checkout@v3
# Step 1: run a standard checkout action
- name: Checkout target repo
uses: actions/checkout@v3
# Step 2: run the sync action
- name: Sync upstream changes
id: sync
uses: aormsby/Fork-Sync-With-Upstream-action@v3.4
with:
upstream_sync_repo: Yidadaa/ChatGPT-Next-Web
upstream_sync_branch: main
target_sync_branch: main
target_repo_token: ${{ secrets.GITHUB_TOKEN }} # automatically generated, no need to set
# Step 2: run the sync action
- name: Sync upstream changes
id: sync
uses: aormsby/Fork-Sync-With-Upstream-action@v3.4
with:
upstream_sync_repo: Yidadaa/ChatGPT-Next-Web
upstream_sync_branch: main
target_sync_branch: main
target_repo_token: ${{ secrets.GITHUB_TOKEN }} # automatically generated, no need to set
# Set test_mode true to run tests instead of the true action!!
test_mode: false
# Set test_mode true to run tests instead of the true action!!
test_mode: false

3
.gitignore vendored
View File

@@ -37,3 +37,6 @@ next-env.d.ts
dev
public/prompts.json
.vscode
.idea

View File

@@ -1,4 +0,0 @@
{
"typescript.tsdk": "node_modules\\typescript\\lib",
"typescript.enablePromptUseWorkspaceTsdk": true
}

156
README.md
View File

@@ -1,82 +1,82 @@
<div align="center">
<img src="./static/icon.svg" alt="预览"/>
<img src="./docs/images/icon.svg" alt="icon"/>
<h1 align="center">ChatGPT Next Web</h1>
一键免费部署你的私人 ChatGPT 网页应用。
One-Click to deploy your own ChatGPT web UI.
[演示 Demo](https://chat-gpt-next-web.vercel.app/) / [反馈 Issues](https://github.com/Yidadaa/ChatGPT-Next-Web/issues) / [加入 Discord](https://discord.gg/zrhvHCr79N) / [QQ 群](https://user-images.githubusercontent.com/16968934/228190818-7dd00845-e9b9-4363-97e5-44c507ac76da.jpeg) / [打赏开发者](https://user-images.githubusercontent.com/16968934/227772541-5bcd52d8-61b7-488c-a203-0330d8006e2b.jpg) / [Donate](#捐赠-donate-usdt)
一键免费部署你的私人 ChatGPT 网页应用。
[演示 Demo](https://chat-gpt-next-web.vercel.app/) / [反馈 Issues](https://github.com/Yidadaa/ChatGPT-Next-Web/issues) / [Join Discord](https://discord.gg/zrhvHCr79N) / [QQ 群](https://user-images.githubusercontent.com/16968934/228190818-7dd00845-e9b9-4363-97e5-44c507ac76da.jpeg) / [打赏开发者](https://user-images.githubusercontent.com/16968934/227772541-5bcd52d8-61b7-488c-a203-0330d8006e2b.jpg)
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FYidadaa%2FChatGPT-Next-Web&env=OPENAI_API_KEY&env=CODE&project-name=chatgpt-next-web&repository-name=ChatGPT-Next-Web)
[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/Yidadaa/ChatGPT-Next-Web)
![主界面](./static/cover.png)
![cover](./docs/images/cover.png)
</div>
## 主要功能
- 在 1 分钟内使用 Vercel **免费一键部署**
- 精心设计的 UI响应式设计支持深色模式
- 极快的首屏加载速度(~85kb
- 海量的内置 prompt 列表,来自[中文](https://github.com/PlexPt/awesome-chatgpt-prompts-zh)和[英文](https://github.com/f/awesome-chatgpt-prompts)
- 自动压缩上下文聊天记录,在节省 Token 的同时支持超长对话
- 一键导出聊天记录,完整的 Markdown 支持
- 拥有自己的域名?好上加好,绑定后即可在任何地方**无障碍**快速访问
## Features
- **Deploy for free with one-click** on Vercel in under 1 minute
- Responsive design, and dark mode
- Fast first screen loading speed (~85kb)
- Responsive design, dark mode and PWA
- Fast first screen loading speed (~100kb)
- Awesome prompts powered by [awesome-chatgpt-prompts-zh](https://github.com/PlexPt/awesome-chatgpt-prompts-zh) and [awesome-chatgpt-prompts](https://github.com/f/awesome-chatgpt-prompts)
- Automatically compresses chat history to support long conversations while also saving your tokens
- One-click export all chat history with full Markdown support
- I18n supported
## 开发计划 Roadmap
## Roadmap
- System Prompt: pin a user defined prompt as system prompt 为每个对话设置系统 Prompt [#138](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/138)
- User Prompt: user can edit and save custom prompts to prompt list 允许用户自行编辑内置 Prompt 列表
- Self-host Model: support llama, alpaca, ChatGLM, BELLE etc. 支持自部署的大语言模型
- Plugins: support network search, caculator, any other apis etc. 插件机制,支持联网搜索、计算器、调用其他平台 api [#165](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/165)
- [x] System Prompt: pin a user defined prompt as system prompt [#138](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/138)
- [ ] User Prompt: user can edit and save custom prompts to prompt list
- [ ] Desktop App with tauri
- [ ] Self-host Model: support llama, alpaca, ChatGLM, BELLE etc.
- [ ] Plugins: support network search, caculator, any other apis etc. [#165](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/165)
### 不会开发的功能 Not in Plan
### Not in Plan
- User login, accounts, cloud sync 用户登录、账号管理、消息云同步
- UI text customize 界面文字自定义
- User login, accounts, cloud sync
- UI text customize
## 开始使用
## 主要功能
- 在 1 分钟内使用 Vercel **免费一键部署**
- 精心设计的 UI响应式设计支持深色模式支持 PWA
- 极快的首屏加载速度(~100kb
- 海量的内置 prompt 列表,来自[中文](https://github.com/PlexPt/awesome-chatgpt-prompts-zh)和[英文](https://github.com/f/awesome-chatgpt-prompts)
- 自动压缩上下文聊天记录,在节省 Token 的同时支持超长对话
- 一键导出聊天记录,完整的 Markdown 支持
- 拥有自己的域名?好上加好,绑定后即可在任何地方**无障碍**快速访问
## 开发计划
- [x] 为每个对话设置系统 Prompt [#138](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/138)
- [ ] 允许用户自行编辑内置 Prompt 列表
- [ ] 使用 tauri 打包桌面应用
- [ ] 支持自部署的大语言模型
- [ ] 插件机制,支持联网搜索、计算器、调用其他平台 api [#165](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/165)
### 不会开发的功能
- 界面文字自定义
- 用户登录、账号管理、消息云同步
1. 准备好你的 [OpenAI API Key](https://platform.openai.com/account/api-keys);
2. 点击右侧按钮开始部署:
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FYidadaa%2FChatGPT-Next-Web&env=OPENAI_API_KEY&env=CODE&project-name=chatgpt-next-web&repository-name=ChatGPT-Next-Web),直接使用 Github 账号登录即可,记得在环境变量页填入 API Key
3. 部署完毕后,即可开始使用;
4. (可选)[绑定自定义域名](https://vercel.com/docs/concepts/projects/domains/add-a-domain)Vercel 分配的域名 DNS 在某些区域被污染了,绑定自定义域名即可直连。
## Get Started
> [简体中文 > 如何开始使用](./README_CN.md#开始使用)
1. Get [OpenAI API Key](https://platform.openai.com/account/api-keys);
2. Click
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FYidadaa%2FChatGPT-Next-Web&env=OPENAI_API_KEY&env=CODE&project-name=chatgpt-next-web&repository-name=ChatGPT-Next-Web);
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FYidadaa%2FChatGPT-Next-Web&env=OPENAI_API_KEY&env=CODE&project-name=chatgpt-next-web&repository-name=ChatGPT-Next-Web), remember that `CODE` is your page password;
3. Enjoy :)
## 保持更新 Keep Updated
## FAQ
> [简体中文 > 常见问题](./docs/faq-cn.md)
如果你按照上述步骤一键部署了自己的项目,可能会发现总是提示“存在更新”的问题,这是由于 Vercel 会默认为你创建一个新项目而不是 fork 本项目,这会导致无法正确地检测更新。
推荐你按照下列步骤重新部署:
We are sorry that there is currently no English version of the FAQ. English users can use translation tools to access the document. We look forward to receiving your PR for an English version of the documentation.
- 删除掉原先的 repo
- fork 本项目;
- 前往 vercel 控制台,删除掉原先的 project然后新建 project选择你刚刚 fork 出来的项目重新进行部署即可;
- 在重新部署的过程中,请手动添加名为 `OPENAI_API_KEY` 的环境变量,并填入你的 api key 作为值。
本项目会持续更新,如果你想让代码库总是保持更新,可以查看 [Github 的文档](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork) 了解如何让 fork 的项目与上游代码同步,建议定期进行同步操作以获得新功能。
你可以 star/watch 本项目或者 follow 作者来及时获得新功能更新通知。
## Keep Updated
> [简体中文 > 如何保持代码更新](./README_CN.md#保持更新)
If you have deployed your own project with just one click following the steps above, you may encounter the issue of "Updates Available" constantly showing up. This is because Vercel will create a new project for you by default instead of forking this project, resulting in the inability to detect updates correctly.
@@ -87,19 +87,14 @@ We recommend that you follow the steps below to re-deploy:
- Go to the Vercel dashboard, delete the original project, then create a new project and select the project you just forked to redeploy;
- Please manually add an environment variable named `OPENAI_API_KEY` and enter your API key as the value during the redeploy process.
This project will be continuously maintained. If you want to keep the code repository up to date, you can check out the [Github documentation](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork) to learn how to synchronize a forked project with upstream code. It is recommended to perform synchronization operations regularly.
This project will be continuously updated, and after forking the project, the upstream code will be automatically synchronized every day without additional operations.
If you want to update instantly, you can check out the [Github documentation](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork) to learn how to synchronize a forked project with upstream code.
You can star or watch this project or follow author to get release notifictions in time.
## 配置密码 Password
本项目提供有限的权限控制功能,请在 Vercel 项目控制面板的环境变量页增加名为 `CODE` 的环境变量,值为用英文逗号分隔的自定义密码:
```
code1,code2,code3
```
增加或修改该环境变量后,请**重新部署**项目使改动生效。
## Access Password
> [简体中文 > 如何增加访问密码](./README_CN.md#配置页面访问密码)
This project provides limited access control. Please add an environment variable named `CODE` on the vercel environment variables page. The value should be passwords separated by comma like this:
@@ -109,26 +104,21 @@ code1,code2,code3
After adding or modifying this environment variable, please redeploy the project for the changes to take effect.
## 环境变量 Environment Variables
## Environment Variables
> [简体中文 > 如何配置 api key、访问密码、接口代理](./README_CN.md#环境变量)
### `OPENAI_API_KEY` (required)
OpanAI 密钥。
Your openai api key.
### `CODE` (optional)
访问密码,可选,可以使用逗号隔开多个密码。
Access passsword, separated by comma.
### `BASE_URL` (optional)
> Default: `api.openai.com`
OpenAI 接口代理 URL。
Override openai api request base url.
### `PROTOCOL` (optional)
@@ -137,38 +127,32 @@ Override openai api request base url.
> Values: `http` | `https`
OpenAI 接口协议。
Override openai api request protocol.
## 开发 Development
点击下方按钮,开始二次开发:
## Development
> [简体中文 > 如何进行二次开发](./README_CN.md#开发)
[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/Yidadaa/ChatGPT-Next-Web)
在开始写代码之前,需要在项目根目录新建一个 `.env.local` 文件,里面填入环境变量:
Before starting development, you must create a new `.env.local` file at project root, and place your api key into it:
```
OPENAI_API_KEY=<your api key here>
```
### 本地开发 Local Development
> 如果你是中国大陆用户,不建议在本地进行开发,除非你能够独立解决 OpenAI API 本地代理问题。
1. 安装 nodejs 和 yarn具体细节请询问 ChatGPT
2. 执行 `yarn install && yarn dev` 即可。
### 本地部署 Local Deployment
### Local Development
```shell
bash <(curl -s https://raw.githubusercontent.com/Yidadaa/ChatGPT-Next-Web/main/scripts/setup.sh)
# 1. install nodejs and yarn first
# 2. config local env vars in `.env.local`
# 3. run
yarn install
yarn dev
```
### 容器部署 Docker Deployment
## Deployment
> [简体中文 > 如何部署到私人服务器](./README_CN.md#部署)
### Docker (Recommended)
```shell
docker pull yidadaa/chatgpt-next-web
@@ -176,23 +160,27 @@ docker pull yidadaa/chatgpt-next-web
docker run -d -p 3000:3000 -e OPENAI_API_KEY="" -e CODE="" yidadaa/chatgpt-next-web
```
## 截图 Screenshots
### Shell
![设置 Settings](./static/settings.png)
```shell
bash <(curl -s https://raw.githubusercontent.com/Yidadaa/ChatGPT-Next-Web/main/scripts/setup.sh)
```
## Screenshots
![更多展示 More](./static/more.png)
![Settings](./docs/images/settings.png)
![More](./docs/images/more.png)
## 鸣谢 Special Thanks
### 捐赠者 Sponsor
## Special Thanks
### Sponsor
[@mushan0x0](https://github.com/mushan0x0)
[@ClarenceDan](https://github.com/ClarenceDan)
[@zhangjia](https://github.com/zhangjia)
[@hoochanlon](https://github.com/hoochanlon)
### 贡献者 Contributor
### Contributor
[Contributors](https://github.com/Yidadaa/ChatGPT-Next-Web/graphs/contributors)

151
README_CN.md Normal file
View File

@@ -0,0 +1,151 @@
<div align="center">
<img src="./static/icon.svg" alt="预览"/>
<h1 align="center">ChatGPT Next Web</h1>
一键免费部署你的私人 ChatGPT 网页应用。
[演示 Demo](https://chat-gpt-next-web.vercel.app/) / [反馈 Issues](https://github.com/Yidadaa/ChatGPT-Next-Web/issues) / [加入 Discord](https://discord.gg/zrhvHCr79N) / [QQ 群](https://user-images.githubusercontent.com/16968934/228190818-7dd00845-e9b9-4363-97e5-44c507ac76da.jpeg) / [打赏开发者](https://user-images.githubusercontent.com/16968934/227772541-5bcd52d8-61b7-488c-a203-0330d8006e2b.jpg) / [Donate](#捐赠-donate-usdt)
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FYidadaa%2FChatGPT-Next-Web&env=OPENAI_API_KEY&env=CODE&project-name=chatgpt-next-web&repository-name=ChatGPT-Next-Web)
[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/Yidadaa/ChatGPT-Next-Web)
![主界面](./static/cover.png)
</div>
## 主要功能
- 在 1 分钟内使用 Vercel **免费一键部署**
- 精心设计的 UI响应式设计支持深色模式
- 极快的首屏加载速度(~100kb
- 海量的内置 prompt 列表,来自[中文](https://github.com/PlexPt/awesome-chatgpt-prompts-zh)和[英文](https://github.com/f/awesome-chatgpt-prompts)
- 自动压缩上下文聊天记录,在节省 Token 的同时支持超长对话
- 一键导出聊天记录,完整的 Markdown 支持
- 拥有自己的域名?好上加好,绑定后即可在任何地方**无障碍**快速访问
## 开始使用
1. 准备好你的 [OpenAI API Key](https://platform.openai.com/account/api-keys);
2. 点击右侧按钮开始部署:
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FYidadaa%2FChatGPT-Next-Web&env=OPENAI_API_KEY&env=CODE&project-name=chatgpt-next-web&repository-name=ChatGPT-Next-Web),直接使用 Github 账号登录即可,记得在环境变量页填入 API Key
3. 部署完毕后,即可开始使用;
4. (可选)[绑定自定义域名](https://vercel.com/docs/concepts/projects/domains/add-a-domain)Vercel 分配的域名 DNS 在某些区域被污染了,绑定自定义域名即可直连。
## 保持更新
如果你按照上述步骤一键部署了自己的项目,可能会发现总是提示“存在更新”的问题,这是由于 Vercel 会默认为你创建一个新项目而不是 fork 本项目,这会导致无法正确地检测更新。
推荐你按照下列步骤重新部署:
- 删除掉原先的 repo
- fork 本项目;
- 前往 vercel 控制台,删除掉原先的 project然后新建 project选择你刚刚 fork 出来的项目重新进行部署即可;
- 在重新部署的过程中,请手动添加名为 `OPENAI_API_KEY` 的环境变量,并填入你的 api key 作为值。
本项目会持续更新,当你 Fork 项目之后,默认会每天自动同步上游代码,无需额外操作。
如果你想让手动立即更新,可以查看 [Github 的文档](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork) 了解如何让 fork 的项目与上游代码同步。
你可以 star/watch 本项目或者 follow 作者来及时获得新功能更新通知。
## 配置页面访问密码
> 配置密码后,用户需要在设置页手动填写访问码才可以正常聊天,否则会通过消息提示未授权状态。
本项目提供有限的权限控制功能,请在 Vercel 项目控制面板的环境变量页增加名为 `CODE` 的环境变量,值为用英文逗号分隔的自定义密码:
```
code1,code2,code3
```
增加或修改该环境变量后,请**重新部署**项目使改动生效。
## 环境变量
> 本项目大多数配置项都通过环境变量来设置。
### `OPENAI_API_KEY` (必填项)
OpanAI 密钥,你在 openai 账户页面申请的 api key。
### `CODE` (可选)
访问密码,可选,可以使用逗号隔开多个密码。
**警告**:如果不填写此项,则任何人都可以直接使用你部署后的网站,可能会导致你的 token 被急速消耗完毕,建议填写此选项。
### `BASE_URL` (可选)
> Default: `api.openai.com`
OpenAI 接口代理 URL如果你手动配置了 openai 接口代理,请填写此选项。
### `PROTOCOL` (可选)
> Default: `https`
> Values: `http` | `https`
OpenAI 代理接口协议,如果遇到 ssl 证书问题,请尝试通过此选项设置为 http。
## 开发
> 强烈不建议在本地进行开发或者部署,由于一些技术原因,导致很难在本地配置好 OpenAI API 代理,除非你能保证可以直。连 OpenAI 服务器
点击下方按钮,开始二次开发:
[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/Yidadaa/ChatGPT-Next-Web)
在开始写代码之前,需要在项目根目录新建一个 `.env.local` 文件,里面填入环境变量:
```
OPENAI_API_KEY=<your api key here>
```
### 本地开发
1. 安装 nodejs 和 yarn具体细节请询问 ChatGPT
2. 执行 `yarn install && yarn dev` 即可。
## 部署
### 容器部署 (推荐)
> 注意docker 版本在大多数时间都会落后最新的版本 1 到 2 天,所以部署后会持续出现“存在更新”的提示,属于正常现象。
```shell
docker pull yidadaa/chatgpt-next-web
docker run -d -p 3000:3000 -e OPENAI_API_KEY="" -e CODE="" yidadaa/chatgpt-next-web
```
### 本地部署
在控制台运行下方命令:
```shell
bash <(curl -s https://raw.githubusercontent.com/Yidadaa/ChatGPT-Next-Web/main/scripts/setup.sh)
```
## 截图 Screenshots
![设置](./static/settings.png)
![更多展示](./static/more.png)
## 鸣谢
### 捐赠者
> 仅列出了部分大额打赏,小额打赏(< 100RMB人数太多在此不再列出敬请谅解。
[@mushan0x0](https://github.com/mushan0x0)
[@ClarenceDan](https://github.com/ClarenceDan)
[@zhangjia](https://github.com/zhangjia)
[@hoochanlon](https://github.com/hoochanlon)
### 贡献者
[见项目贡献者列表](https://github.com/Yidadaa/ChatGPT-Next-Web/graphs/contributors)
## 开源协议
> 反对 996从我开始。
[Anti 996 License](https://github.com/kattgu7/Anti-996-License/blob/master/LICENSE_CN_EN)

View File

@@ -53,6 +53,9 @@ export async function POST(req: NextRequest) {
return new Response(stream);
} catch (error) {
console.error("[Chat Stream]", error);
return new Response(
["```json\n", JSON.stringify(error, null, " "), "\n```"].join(""),
);
}
}

View File

@@ -63,6 +63,14 @@
font-size: 12px;
font-weight: bold;
margin-bottom: 10px;
display: flex;
justify-content: space-between;
align-items: center;
.memory-prompt-action {
display: flex;
align-items: center;
}
}
.memory-prompt-content {

View File

@@ -1,5 +1,5 @@
import { useDebouncedCallback } from "use-debounce";
import { useState, useRef, useEffect, useLayoutEffect } from "react";
import { memo, useState, useRef, useEffect, useLayoutEffect } from "react";
import SendWhiteIcon from "../icons/send-white.svg";
import BrainIcon from "../icons/brain.svg";
@@ -16,14 +16,15 @@ import {
Message,
SubmitKey,
useChatStore,
ChatSession,
BOT_HELLO,
ROLES,
createMessage,
} from "../store";
import {
copyToClipboard,
downloadAs,
getEmojiUrl,
isMobileScreen,
selectOrCopy,
} from "../utils";
@@ -38,11 +39,14 @@ import { IconButton } from "./button";
import styles from "./home.module.scss";
import chatStyle from "./chat.module.scss";
import { Modal, showModal, showToast } from "./ui-lib";
import { Input, Modal, showModal, showToast } from "./ui-lib";
const Markdown = dynamic(async () => (await import("./markdown")).Markdown, {
loading: () => <LoadingIcon />,
});
const Markdown = dynamic(
async () => memo((await import("./markdown")).Markdown),
{
loading: () => <LoadingIcon />,
},
);
const Emoji = dynamic(async () => (await import("emoji-picker-react")).Emoji, {
loading: () => <LoadingIcon />,
@@ -57,7 +61,7 @@ export function Avatar(props: { role: Message["role"] }) {
return (
<div className={styles["user-avtar"]}>
<Emoji unified={config.avatar} size={18} />
<Emoji unified={config.avatar} size={18} getEmojiUrl={getEmojiUrl} />
</div>
);
}
@@ -67,7 +71,9 @@ function exportMessages(messages: Message[], topic: string) {
`# ${topic}\n\n` +
messages
.map((m) => {
return m.role === "user" ? `## ${m.content}` : m.content.trim();
return m.role === "user"
? `## ${Locale.Export.MessageFromYou}:\n${m.content}`
: `## ${Locale.Export.MessageFromChatGPT}:\n${m.content.trim()}`;
})
.join("\n\n");
const filename = `${topic}.md`;
@@ -145,6 +151,16 @@ function PromptToast(props: {
title={Locale.Context.Edit}
onClose={() => props.setShowModal(false)}
actions={[
<IconButton
key="reset"
icon={<CopyIcon />}
bordered
text={Locale.Memory.Reset}
onClick={() =>
confirm(Locale.Memory.ResetConfirm) &&
chatStore.resetSession()
}
/>,
<IconButton
key="copy"
icon={<CopyIcon />}
@@ -155,7 +171,6 @@ function PromptToast(props: {
]}
>
<>
{" "}
<div className={chatStyle["context-prompt"]}>
{context.map((c, i) => (
<div className={chatStyle["context-prompt-row"]} key={i}>
@@ -175,17 +190,18 @@ function PromptToast(props: {
</option>
))}
</select>
<input
<Input
value={c.content}
type="text"
className={chatStyle["context-content"]}
onChange={(e) =>
rows={1}
onInput={(e) =>
updateContextPrompt(i, {
...c,
content: e.target.value as any,
content: e.currentTarget.value as any,
})
}
></input>
/>
<IconButton
icon={<DeleteIcon />}
className={chatStyle["context-delete-button"]}
@@ -213,8 +229,24 @@ function PromptToast(props: {
</div>
<div className={chatStyle["memory-prompt"]}>
<div className={chatStyle["memory-prompt-title"]}>
{Locale.Memory.Title} ({session.lastSummarizeIndex} of{" "}
{session.messages.length})
<span>
{Locale.Memory.Title} ({session.lastSummarizeIndex} of{" "}
{session.messages.length})
</span>
<label className={chatStyle["memory-prompt-action"]}>
{Locale.Memory.Send}
<input
type="checkbox"
checked={session.sendMemory}
onChange={() =>
chatStore.updateCurrentSession(
(session) =>
(session.sendMemory = !session.sendMemory),
)
}
></input>
</label>
</div>
<div className={chatStyle["memory-prompt-content"]}>
{session.memoryPrompt || Locale.Memory.EmptyContent}
@@ -377,12 +409,13 @@ export function Chat(props: {
chatStore.onUserInput(userInput).then(() => setIsLoading(false));
setUserInput("");
setPromptHints([]);
inputRef.current?.focus();
if (!isMobileScreen()) inputRef.current?.focus();
setAutoScroll(true);
};
// stop response
const onUserStop = (messageIndex: number) => {
ControllerPool.stop(sessionIndex, messageIndex);
const onUserStop = (messageId: number) => {
ControllerPool.stop(sessionIndex, messageId);
};
// check if should send message
@@ -412,6 +445,9 @@ export function Chat(props: {
chatStore
.onUserInput(messages[i].content)
.then(() => setIsLoading(false));
chatStore.updateCurrentSession((session) =>
session.messages.splice(i, 2),
);
inputRef.current?.focus();
return;
}
@@ -436,9 +472,10 @@ export function Chat(props: {
isLoading
? [
{
role: "assistant",
content: "……",
date: new Date().toLocaleString(),
...createMessage({
role: "assistant",
content: "……",
}),
preview: true,
},
]
@@ -448,9 +485,10 @@ export function Chat(props: {
userInput.length > 0 && config.sendPreviewBubble
? [
{
role: "user",
content: userInput,
date: new Date().toLocaleString(),
...createMessage({
role: "user",
content: userInput,
}),
preview: true,
},
]
@@ -461,7 +499,9 @@ export function Chat(props: {
// Auto focus
useEffect(() => {
if (props.sideBarShowing && isMobileScreen()) return;
inputRef.current?.focus();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
@@ -513,7 +553,10 @@ export function Chat(props: {
bordered
title={Locale.Chat.Actions.Export}
onClick={() => {
exportMessages(session.messages, session.topic);
exportMessages(
session.messages.filter((msg) => !msg.isError),
session.topic,
);
}}
/>
</div>
@@ -530,8 +573,11 @@ export function Chat(props: {
className={styles["chat-body"]}
ref={scrollRef}
onScroll={(e) => onChatBodyScroll(e.currentTarget)}
onMouseOver={() => inputRef.current?.blur()}
onTouchStart={() => inputRef.current?.blur()}
onWheel={(e) => setAutoScroll(hitBottom && e.deltaY > 0)}
onTouchStart={() => {
inputRef.current?.blur();
setAutoScroll(false);
}}
>
{messages.map((message, i) => {
const isUser = message.role === "user";
@@ -559,7 +605,7 @@ export function Chat(props: {
{message.streaming ? (
<div
className={styles["chat-message-top-action"]}
onClick={() => onUserStop(i)}
onClick={() => onUserStop(message.id ?? i)}
>
{Locale.Chat.Actions.Stop}
</div>

47
app/components/error.tsx Normal file
View File

@@ -0,0 +1,47 @@
import React from "react";
import { IconButton } from "./button";
import GithubIcon from "../icons/github.svg";
import { ISSUE_URL } from "../constant";
interface IErrorBoundaryState {
hasError: boolean;
error: Error | null;
info: React.ErrorInfo | null;
}
export class ErrorBoundary extends React.Component<any, IErrorBoundaryState> {
constructor(props: any) {
super(props);
this.state = { hasError: false, error: null, info: null };
}
componentDidCatch(error: Error, info: React.ErrorInfo) {
// Update state with error details
this.setState({ hasError: true, error, info });
}
render() {
if (this.state.hasError) {
// Render error message
return (
<div className="error">
<h2>Oops, something went wrong!</h2>
<pre>
<code>{this.state.error?.toString()}</code>
<code>{this.state.info?.componentStack}</code>
</pre>
<a href={ISSUE_URL} className="report">
<IconButton
text="Report This Error"
icon={<GithubIcon />}
bordered
/>
</a>
</div>
);
}
// if no error occurred, render children
return this.props.children;
}
}

View File

@@ -1,6 +1,8 @@
"use client";
import { useState, useRef, useEffect, useLayoutEffect } from "react";
require("../polyfill");
import { useState, useEffect } from "react";
import { IconButton } from "./button";
import styles from "./home.module.scss";
@@ -14,25 +16,15 @@ import AddIcon from "../icons/add.svg";
import LoadingIcon from "../icons/three-dots.svg";
import CloseIcon from "../icons/close.svg";
import {
Message,
SubmitKey,
useChatStore,
ChatSession,
BOT_HELLO,
} from "../store";
import {
copyToClipboard,
downloadAs,
isMobileScreen,
selectOrCopy,
} from "../utils";
import { useChatStore } from "../store";
import { isMobileScreen } from "../utils";
import Locale from "../locales";
import { ChatList } from "./chat-list";
import { Chat } from "./chat";
import dynamic from "next/dynamic";
import { REPO_URL } from "../constant";
import { ErrorBoundary } from "./error";
export function Loading(props: { noLogo?: boolean }) {
return (
@@ -60,11 +52,23 @@ function useSwitchTheme() {
document.body.classList.add("light");
}
const themeColor = getComputedStyle(document.body)
.getPropertyValue("--theme-color")
.trim();
const metaDescription = document.querySelector('meta[name="theme-color"]');
metaDescription?.setAttribute("content", themeColor);
const metaDescriptionDark = document.querySelector(
'meta[name="theme-color"][media]',
);
const metaDescriptionLight = document.querySelector(
'meta[name="theme-color"]:not([media])',
);
if (config.theme === "auto") {
metaDescriptionDark?.setAttribute("content", "#151515");
metaDescriptionLight?.setAttribute("content", "#fafafa");
} else {
const themeColor = getComputedStyle(document.body)
.getPropertyValue("--theme-color")
.trim();
metaDescriptionDark?.setAttribute("content", themeColor);
metaDescriptionLight?.setAttribute("content", themeColor);
}
}, [config.theme]);
}
@@ -78,7 +82,7 @@ const useHasHydrated = () => {
return hasHydrated;
};
export function Home() {
function _Home() {
const [createNewSession, currentIndex, removeSession] = useChatStore(
(state) => [
state.newSession,
@@ -191,3 +195,11 @@ export function Home() {
</div>
);
}
export function Home() {
return (
<ErrorBoundary>
<_Home></_Home>
</ErrorBoundary>
);
}

View File

@@ -67,6 +67,7 @@ export function Markdown(props: { content: string }) {
components={{
pre: PreCode,
}}
linkTarget={'_blank'}
>
{props.content}
</ReactMarkdown>

View File

@@ -18,3 +18,12 @@
.avatar {
cursor: pointer;
}
.password-input {
display: flex;
justify-content: flex-end;
.password-eye {
margin-right: 4px;
}
}

View File

@@ -1,4 +1,4 @@
import { useState, useEffect, useRef, useMemo } from "react";
import { useState, useEffect, useMemo, HTMLProps } from "react";
import EmojiPicker, { Theme as EmojiTheme } from "emoji-picker-react";
@@ -8,6 +8,8 @@ import ResetIcon from "../icons/reload.svg";
import CloseIcon from "../icons/close.svg";
import ClearIcon from "../icons/clear.svg";
import EditIcon from "../icons/edit.svg";
import EyeIcon from "../icons/eye.svg";
import EyeOffIcon from "../icons/eye-off.svg";
import { List, ListItem, Popover, showToast } from "./ui-lib";
@@ -19,15 +21,17 @@ import {
ALL_MODELS,
useUpdateStore,
useAccessStore,
ModalConfigValidator,
} from "../store";
import { Avatar } from "./chat";
import Locale, { AllLangs, changeLang, getLang } from "../locales";
import { getCurrentVersion } from "../utils";
import { getCurrentVersion, getEmojiUrl } from "../utils";
import Link from "next/link";
import { UPDATE_URL } from "../constant";
import { SearchService, usePromptStore } from "../store/prompt";
import { requestUsage } from "../requests";
import { ErrorBoundary } from "./error";
function SettingItem(props: {
title: string;
@@ -47,6 +51,25 @@ function SettingItem(props: {
);
}
function PasswordInput(props: HTMLProps<HTMLInputElement>) {
const [visible, setVisible] = useState(false);
function changeVisibility() {
setVisible(!visible);
}
return (
<div className={styles["password-input"]}>
<IconButton
icon={visible ? <EyeIcon /> : <EyeOffIcon />}
onClick={changeVisibility}
className={styles["password-eye"]}
/>
<input {...props} type={visible ? "text" : "password"} />
</div>
);
}
export function Settings(props: { closeSettings: () => void }) {
const [showEmojiPicker, setShowEmojiPicker] = useState(false);
const [config, updateConfig, resetConfig, clearAllData, clearSessions] =
@@ -73,29 +96,22 @@ export function Settings(props: { closeSettings: () => void }) {
const [usage, setUsage] = useState<{
used?: number;
subscription?: number;
}>();
const [loadingUsage, setLoadingUsage] = useState(false);
function checkUsage() {
setLoadingUsage(true);
requestUsage()
.then((res) =>
setUsage({
used: res,
}),
)
.then((res) => setUsage(res))
.finally(() => {
setLoadingUsage(false);
});
}
useEffect(() => {
checkUpdate();
checkUsage();
}, []);
const accessStore = useAccessStore();
const enabledAccessControl = useMemo(
() => accessStore.enabledAccessControl(),
// eslint-disable-next-line react-hooks/exhaustive-deps
[],
);
@@ -103,8 +119,16 @@ export function Settings(props: { closeSettings: () => void }) {
const builtinCount = SearchService.count.builtin;
const customCount = promptStore.prompts.size ?? 0;
const showUsage = !!accessStore.token || !!accessStore.accessCode;
useEffect(() => {
checkUpdate();
showUsage && checkUsage();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<>
<ErrorBoundary>
<div className={styles["window-header"]}>
<div className={styles["window-header-title"]}>
<div className={styles["window-header-main-title"]}>
@@ -150,6 +174,7 @@ export function Settings(props: { closeSettings: () => void }) {
<EmojiPicker
lazyLoadEmojis
theme={EmojiTheme.AUTO}
getEmojiUrl={getEmojiUrl}
onEmojiClick={(e) => {
updateConfig((config) => (config.avatar = e.unified));
setShowEmojiPicker(false);
@@ -327,14 +352,14 @@ export function Settings(props: { closeSettings: () => void }) {
title={Locale.Settings.AccessCode.Title}
subTitle={Locale.Settings.AccessCode.SubTitle}
>
<input
<PasswordInput
value={accessStore.accessCode}
type="text"
placeholder={Locale.Settings.AccessCode.Placeholder}
onChange={(e) => {
accessStore.updateCode(e.currentTarget.value);
}}
></input>
/>
</SettingItem>
) : (
<></>
@@ -344,25 +369,30 @@ export function Settings(props: { closeSettings: () => void }) {
title={Locale.Settings.Token.Title}
subTitle={Locale.Settings.Token.SubTitle}
>
<input
<PasswordInput
value={accessStore.token}
type="text"
placeholder={Locale.Settings.Token.Placeholder}
onChange={(e) => {
accessStore.updateToken(e.currentTarget.value);
}}
></input>
/>
</SettingItem>
<SettingItem
title={Locale.Settings.Usage.Title}
subTitle={
loadingUsage
? Locale.Settings.Usage.IsChecking
: Locale.Settings.Usage.SubTitle(usage?.used ?? "[?]")
showUsage
? loadingUsage
? Locale.Settings.Usage.IsChecking
: Locale.Settings.Usage.SubTitle(
usage?.used ?? "[?]",
usage?.subscription ?? "[?]",
)
: Locale.Settings.Usage.NoAccess
}
>
{loadingUsage ? (
{!showUsage || loadingUsage ? (
<div />
) : (
<IconButton
@@ -383,7 +413,7 @@ export function Settings(props: { closeSettings: () => void }) {
value={config.historyMessageCount}
min="0"
max="25"
step="2"
step="1"
onChange={(e) =>
updateConfig(
(config) =>
@@ -420,7 +450,9 @@ export function Settings(props: { closeSettings: () => void }) {
onChange={(e) => {
updateConfig(
(config) =>
(config.modelConfig.model = e.currentTarget.value),
(config.modelConfig.model = ModalConfigValidator.model(
e.currentTarget.value,
)),
);
}}
>
@@ -437,7 +469,7 @@ export function Settings(props: { closeSettings: () => void }) {
>
<input
type="range"
value={config.modelConfig.temperature.toFixed(1)}
value={config.modelConfig.temperature?.toFixed(1)}
min="0"
max="2"
step="0.1"
@@ -445,7 +477,9 @@ export function Settings(props: { closeSettings: () => void }) {
updateConfig(
(config) =>
(config.modelConfig.temperature =
e.currentTarget.valueAsNumber),
ModalConfigValidator.temperature(
e.currentTarget.valueAsNumber,
)),
);
}}
></input>
@@ -457,13 +491,15 @@ export function Settings(props: { closeSettings: () => void }) {
<input
type="number"
min={100}
max={4096}
max={32000}
value={config.modelConfig.max_tokens}
onChange={(e) =>
updateConfig(
(config) =>
(config.modelConfig.max_tokens =
e.currentTarget.valueAsNumber),
ModalConfigValidator.max_tokens(
e.currentTarget.valueAsNumber,
)),
)
}
></input>
@@ -474,7 +510,7 @@ export function Settings(props: { closeSettings: () => void }) {
>
<input
type="range"
value={config.modelConfig.presence_penalty.toFixed(1)}
value={config.modelConfig.presence_penalty?.toFixed(1)}
min="-2"
max="2"
step="0.5"
@@ -482,13 +518,15 @@ export function Settings(props: { closeSettings: () => void }) {
updateConfig(
(config) =>
(config.modelConfig.presence_penalty =
e.currentTarget.valueAsNumber),
ModalConfigValidator.presence_penalty(
e.currentTarget.valueAsNumber,
)),
);
}}
></input>
</SettingItem>
</List>
</div>
</>
</ErrorBoundary>
);
}

View File

@@ -141,6 +141,16 @@
}
}
.input {
border: var(--border-in-light);
border-radius: 10px;
padding: 10px;
font-family: inherit;
background-color: var(--white);
color: var(--black);
resize: none;
}
@media only screen and (max-width: 600px) {
.modal-container {
width: 90vw;

View File

@@ -2,6 +2,7 @@ import styles from "./ui-lib.module.scss";
import LoadingIcon from "../icons/three-dots.svg";
import CloseIcon from "../icons/close.svg";
import { createRoot } from "react-dom/client";
import React from "react";
export function Popover(props: {
children: JSX.Element;
@@ -140,3 +141,17 @@ export function showToast(content: string, delay = 3000) {
root.render(<Toast content={content} />);
}
export type InputProps = React.HTMLProps<HTMLTextAreaElement> & {
autoHeight?: boolean;
rows?: number;
};
export function Input(props: InputProps) {
return (
<textarea
{...props}
className={`${styles["input"]} ${props.className}`}
></textarea>
);
}

View File

@@ -10,6 +10,7 @@
.window-header-title {
max-width: calc(100% - 100px);
overflow: hidden;
.window-header-main-title {
font-size: 20px;

View File

@@ -1,6 +1,7 @@
export const OWNER = "Yidadaa";
export const REPO = "ChatGPT-Next-Web";
export const REPO_URL = `https://github.com/${OWNER}/${REPO}`;
export const UPDATE_URL = `${REPO_URL}#%E4%BF%9D%E6%8C%81%E6%9B%B4%E6%96%B0-keep-updated`;
export const ISSUE_URL = `https://github.com/${OWNER}/${REPO}/issues`;
export const UPDATE_URL = `${REPO_URL}#keep-updated`;
export const FETCH_COMMIT_URL = `https://api.github.com/repos/${OWNER}/${REPO}/commits?per_page=1`;
export const FETCH_TAG_URL = `https://api.github.com/repos/${OWNER}/${REPO}/tags?per_page=1`;

4
app/icons/eye-off.svg Normal file
View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M19.7071 5.70711C20.0976 5.31658 20.0976 4.68342 19.7071 4.29289C19.3166 3.90237 18.6834 3.90237 18.2929 4.29289L14.032 8.55382C13.4365 8.20193 12.7418 8 12 8C9.79086 8 8 9.79086 8 12C8 12.7418 8.20193 13.4365 8.55382 14.032L4.29289 18.2929C3.90237 18.6834 3.90237 19.3166 4.29289 19.7071C4.68342 20.0976 5.31658 20.0976 5.70711 19.7071L9.96803 15.4462C10.5635 15.7981 11.2582 16 12 16C14.2091 16 16 14.2091 16 12C16 11.2582 15.7981 10.5635 15.4462 9.96803L19.7071 5.70711ZM12.518 10.0677C12.3528 10.0236 12.1792 10 12 10C10.8954 10 10 10.8954 10 12C10 12.1792 10.0236 12.3528 10.0677 12.518L12.518 10.0677ZM11.482 13.9323L13.9323 11.482C13.9764 11.6472 14 11.8208 14 12C14 13.1046 13.1046 14 12 14C11.8208 14 11.6472 13.9764 11.482 13.9323ZM15.7651 4.8207C14.6287 4.32049 13.3675 4 12 4C9.14754 4 6.75717 5.39462 4.99812 6.90595C3.23268 8.42276 2.00757 10.1376 1.46387 10.9698C1.05306 11.5985 1.05306 12.4015 1.46387 13.0302C1.92276 13.7326 2.86706 15.0637 4.21194 16.3739L5.62626 14.9596C4.4555 13.8229 3.61144 12.6531 3.18002 12C3.6904 11.2274 4.77832 9.73158 6.30147 8.42294C7.87402 7.07185 9.81574 6 12 6C12.7719 6 13.5135 6.13385 14.2193 6.36658L15.7651 4.8207ZM12 18C11.2282 18 10.4866 17.8661 9.78083 17.6334L8.23496 19.1793C9.37136 19.6795 10.6326 20 12 20C14.8525 20 17.2429 18.6054 19.002 17.0941C20.7674 15.5772 21.9925 13.8624 22.5362 13.0302C22.947 12.4015 22.947 11.5985 22.5362 10.9698C22.0773 10.2674 21.133 8.93627 19.7881 7.62611L18.3738 9.04043C19.5446 10.1771 20.3887 11.3469 20.8201 12C20.3097 12.7726 19.2218 14.2684 17.6986 15.5771C16.1261 16.9282 14.1843 18 12 18Z" fill="#000000"/>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

4
app/icons/eye.svg Normal file
View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.30147 15.5771C4.77832 14.2684 3.6904 12.7726 3.18002 12C3.6904 11.2274 4.77832 9.73158 6.30147 8.42294C7.87402 7.07185 9.81574 6 12 6C14.1843 6 16.1261 7.07185 17.6986 8.42294C19.2218 9.73158 20.3097 11.2274 20.8201 12C20.3097 12.7726 19.2218 14.2684 17.6986 15.5771C16.1261 16.9282 14.1843 18 12 18C9.81574 18 7.87402 16.9282 6.30147 15.5771ZM12 4C9.14754 4 6.75717 5.39462 4.99812 6.90595C3.23268 8.42276 2.00757 10.1376 1.46387 10.9698C1.05306 11.5985 1.05306 12.4015 1.46387 13.0302C2.00757 13.8624 3.23268 15.5772 4.99812 17.0941C6.75717 18.6054 9.14754 20 12 20C14.8525 20 17.2429 18.6054 19.002 17.0941C20.7674 15.5772 21.9925 13.8624 22.5362 13.0302C22.947 12.4015 22.947 11.5985 22.5362 10.9698C21.9925 10.1376 20.7674 8.42276 19.002 6.90595C17.2429 5.39462 14.8525 4 12 4ZM10 12C10 10.8954 10.8955 10 12 10C13.1046 10 14 10.8954 14 12C14 13.1046 13.1046 14 12 14C10.8955 14 10 13.1046 10 12ZM12 8C9.7909 8 8.00004 9.79086 8.00004 12C8.00004 14.2091 9.7909 16 12 16C14.2092 16 16 14.2091 16 12C16 9.79086 14.2092 8 12 8Z" fill="#000000"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -21,7 +21,7 @@ export const metadata = {
description: "Your personal ChatGPT Chat Bot.",
appleWebApp: {
title: "ChatGPT Next Web",
statusBarStyle: "black-translucent",
statusBarStyle: "default",
},
themeColor: "#fafafa",
};
@@ -53,6 +53,11 @@ export default function RootLayout({
name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"
/>
<meta
name="theme-color"
content="#151515"
media="(prefers-color-scheme: dark)"
/>
<Meta />
<link rel="manifest" href="/site.webmanifest"></link>
<link rel="preconnect" href="https://fonts.googleapis.com"></link>

View File

@@ -3,7 +3,7 @@ import { SubmitKey } from "../store/app";
const cn = {
WIP: "该功能仍在开发中……",
Error: {
Unauthorized: "现在是未授权状态,请在设置页填写授权码。",
Unauthorized: "现在是未授权状态,请在设置页输入访问密码。",
},
ChatItem: {
ChatItemCount: (count: number) => `${count} 条对话`,
@@ -33,11 +33,16 @@ const cn = {
Title: "导出聊天记录为 Markdown",
Copy: "全部复制",
Download: "下载文件",
MessageFromYou: "来自你的消息",
MessageFromChatGPT: "来自 ChatGPT 的消息",
},
Memory: {
Title: "历史记忆",
EmptyContent: "尚未记忆",
Copy: "全部复制",
Send: "发送记忆",
Copy: "复制记忆",
Reset: "重置对话",
ResetConfirm: "重置后将清空当前对话记录以及历史记忆,确认重置?",
},
Home: {
NewChat: "新的聊天",
@@ -58,6 +63,7 @@ const cn = {
en: "English",
tw: "繁體中文",
es: "Español",
it: "Italiano",
},
},
Avatar: "头像",
@@ -98,21 +104,22 @@ const cn = {
},
Token: {
Title: "API Key",
SubTitle: "使用自己的 Key 可绕过受控访问限制",
SubTitle: "使用自己的 Key 可绕过密码访问限制",
Placeholder: "OpenAI API Key",
},
Usage: {
Title: "账户余额",
SubTitle(used: any) {
return `本月已使用 $${used}`;
Title: "余额查询",
SubTitle(used: any, total: any) {
return `本月已使用 $${used},订阅总额 $${total}`;
},
IsChecking: "正在检查…",
Check: "重新检查",
NoAccess: "输入 API Key 或访问密码查看余额",
},
AccessCode: {
Title: "访问码",
SubTitle: "现在是受控访问状态",
Placeholder: "请输入访问码",
Title: "访问码",
SubTitle: "现在是未授权访问状态",
Placeholder: "请输入访问码",
},
Model: "模型 (model)",
Temperature: {

View File

@@ -35,11 +35,17 @@ const en: LocaleType = {
Title: "All Messages",
Copy: "Copy All",
Download: "Download",
MessageFromYou: "Message From You",
MessageFromChatGPT: "Message From ChatGPT",
},
Memory: {
Title: "Memory Prompt",
EmptyContent: "Nothing yet.",
Copy: "Copy All",
Send: "Send Memory",
Copy: "Copy Memory",
Reset: "Reset Session",
ResetConfirm:
"Resetting will clear the current conversation history and historical memory. Are you sure you want to reset?",
},
Home: {
NewChat: "New Chat",
@@ -60,6 +66,7 @@ const en: LocaleType = {
en: "English",
tw: "繁體中文",
es: "Español",
it: "Italiano",
},
},
Avatar: "Avatar",
@@ -105,11 +112,12 @@ const en: LocaleType = {
},
Usage: {
Title: "Account Balance",
SubTitle(used: any) {
return `Used this month $${used}`;
SubTitle(used: any, total: any) {
return `Used this month $${used}, subscription $${total}`;
},
IsChecking: "Checking...",
Check: "Check Again",
NoAccess: "Enter API Key to check balance",
},
AccessCode: {
Title: "Access Code",

View File

@@ -35,11 +35,17 @@ const es: LocaleType = {
Title: "Todos los mensajes",
Copy: "Copiar todo",
Download: "Descargar",
MessageFromYou: "Mensaje de ti",
MessageFromChatGPT: "Mensaje de ChatGPT",
},
Memory: {
Title: "Historial de memoria",
EmptyContent: "Aún no hay nada.",
Copy: "Copiar todo",
Send: "Send Memory",
Reset: "Reset Session",
ResetConfirm:
"Resetting will clear the current conversation history and historical memory. Are you sure you want to reset?",
},
Home: {
NewChat: "Nuevo chat",
@@ -60,6 +66,7 @@ const es: LocaleType = {
en: "Inglés",
tw: "繁體中文",
es: "Español",
it: "Italiano",
},
},
Avatar: "Avatar",
@@ -105,11 +112,12 @@ const es: LocaleType = {
},
Usage: {
Title: "Saldo de la cuenta",
SubTitle(used: any) {
return `Usado $${used}`;
SubTitle(used: any, total: any) {
return `Usado $${used}, subscription $${total}`;
},
IsChecking: "Comprobando...",
Check: "Comprobar de nuevo",
NoAccess: "Introduzca la clave API para comprobar el saldo",
},
AccessCode: {
Title: "Código de acceso",

View File

@@ -2,10 +2,11 @@ import CN from "./cn";
import EN from "./en";
import TW from "./tw";
import ES from "./es";
import IT from "./it";
export type { LocaleType } from "./cn";
export const AllLangs = ["cn", "tw", "en", "es"] as const;
export const AllLangs = ["en", "cn", "tw", "es", "it"] as const;
type Lang = (typeof AllLangs)[number];
const LANG_KEY = "lang";
@@ -47,6 +48,8 @@ export function getLang(): Lang {
return "tw";
} else if (lang.includes("es")) {
return "es";
} else if (lang.includes("it")) {
return "it";
} else {
return "en";
}
@@ -57,4 +60,4 @@ export function changeLang(lang: Lang) {
location.reload();
}
export default { en: EN, cn: CN, tw: TW, es: ES }[getLang()];
export default { en: EN, cn: CN, tw: TW, es: ES, it: IT }[getLang()];

171
app/locales/it.ts Normal file
View File

@@ -0,0 +1,171 @@
import { SubmitKey } from "../store/app";
import type { LocaleType } from "./index";
const it: LocaleType = {
WIP: "Work in progress...",
Error: {
Unauthorized:
"Accesso non autorizzato, inserire il codice di accesso nella pagina delle impostazioni.",
},
ChatItem: {
ChatItemCount: (count: number) => `${count} messaggi`,
},
Chat: {
SubTitle: (count: number) => `${count} messaggi con ChatGPT`,
Actions: {
ChatList: "Vai alla Chat List",
CompressedHistory: "Prompt di memoria della cronologia compressa",
Export: "Esportazione di tutti i messaggi come Markdown",
Copy: "Copia",
Stop: "Stop",
Retry: "Riprova",
},
Rename: "Rinomina Chat",
Typing: "Typing…",
Input: (submitKey: string) => {
var inputHints = `Scrivi qualcosa e premi ${submitKey} per inviare`;
if (submitKey === String(SubmitKey.Enter)) {
inputHints += ", premi Shift + Enter per andare a capo";
}
return inputHints;
},
Send: "Invia",
},
Export: {
Title: "Tutti i messaggi",
Copy: "Copia tutto",
Download: "Scarica",
MessageFromYou: "Messaggio da te",
MessageFromChatGPT: "Messaggio da ChatGPT",
},
Memory: {
Title: "Prompt di memoria",
EmptyContent: "Vuoto.",
Copy: "Copia tutto",
Send: "Send Memory",
Reset: "Reset Session",
ResetConfirm:
"Resetting will clear the current conversation history and historical memory. Are you sure you want to reset?",
},
Home: {
NewChat: "Nuova Chat",
DeleteChat: "Confermare la cancellazione della conversazione selezionata?",
},
Settings: {
Title: "Impostazioni",
SubTitle: "Tutte le impostazioni",
Actions: {
ClearAll: "Cancella tutti i dati",
ResetAll: "Resetta tutte le impostazioni",
Close: "Chiudi",
},
Lang: {
Name: "Lingue",
Options: {
cn: "简体中文",
en: "English",
tw: "繁體中文",
es: "Español",
it: "Italiano",
},
},
Avatar: "Avatar",
FontSize: {
Title: "Dimensione carattere",
SubTitle: "Regolare la dimensione dei caratteri del contenuto della chat",
},
Update: {
Version: (x: string) => `Versione: ${x}`,
IsLatest: "Ultima versione",
CheckUpdate: "Controlla aggiornamenti",
IsChecking: "Sto controllando gli aggiornamenti...",
FoundUpdate: (x: string) => `Trovata nuova versione: ${x}`,
GoToUpdate: "Aggiorna",
},
SendKey: "Tasto invia",
Theme: "tema",
TightBorder: "Bordi stretti",
SendPreviewBubble: "Invia l'anteprima della bolla",
Prompt: {
Disable: {
Title: "Disabilita l'auto completamento",
SubTitle: "Input / per attivare il completamento automatico",
},
List: "Elenco dei suggerimenti",
ListCount: (builtin: number, custom: number) =>
`${builtin} built-in, ${custom} user-defined`,
Edit: "Modifica",
},
HistoryCount: {
Title: "Conteggio dei messaggi allegati",
SubTitle: "Numero di messaggi inviati allegati per richiesta",
},
CompressThreshold: {
Title: "Soglia di compressione della cronologia",
SubTitle:
"Comprimerà se la lunghezza dei messaggi non compressi supera il valore",
},
Token: {
Title: "Chiave API",
SubTitle:
"Utilizzare la chiave per ignorare il limite del codice di accesso",
Placeholder: "OpenAI API Key",
},
Usage: {
Title: "Bilancio Account",
SubTitle(used: any, total: any) {
return `Usato in questo mese $${used}, subscription $${total}`;
},
IsChecking: "Controllando...",
Check: "Controlla ancora",
NoAccess: "Inserire la chiave API per controllare il saldo",
},
AccessCode: {
Title: "Codice d'accesso",
SubTitle: "Controllo d'accesso abilitato",
Placeholder: "Inserisci il codice d'accesso",
},
Model: "Modello GPT",
Temperature: {
Title: "Temperature",
SubTitle: "Un valore maggiore rende l'output più casuale",
},
MaxTokens: {
Title: "Token massimi",
SubTitle: "Lunghezza massima dei token in ingresso e dei token generati",
},
PresencePenlty: {
Title: "Penalità di presenza",
SubTitle:
"Un valore maggiore aumenta la probabilità di parlare di nuovi argomenti",
},
},
Store: {
DefaultTopic: "Nuova conversazione",
BotHello: "Ciao, come posso aiutarti oggi?",
Error: "Qualcosa è andato storto, riprova più tardi.",
Prompt: {
History: (content: string) =>
"Questo è un riassunto della cronologia delle chat tra l'IA e l'utente:" +
content,
Topic:
"Si prega di generare un titolo di quattro o cinque parole che riassuma la nostra conversazione senza alcuna traccia, punteggiatura, virgolette, punti, simboli o testo aggiuntivo. Rimuovere le virgolette",
Summarize:
"Riassumi brevemente la nostra discussione in 200 caratteri o meno per usarla come spunto per una futura conversazione.",
},
ConfirmClearAll:
"Confermi la cancellazione di tutti i dati della chat e delle impostazioni?",
},
Copy: {
Success: "Copiato sugli appunti",
Failed:
"Copia fallita, concedere l'autorizzazione all'accesso agli appunti",
},
Context: {
Toast: (x: any) => `Con ${x} prompts contestuali`,
Edit: "Prompt contestuali e di memoria",
Add: "Aggiungi altro",
},
};
export default it;

View File

@@ -4,7 +4,7 @@ import type { LocaleType } from "./index";
const tw: LocaleType = {
WIP: "該功能仍在開發中……",
Error: {
Unauthorized: "目前您的狀態是未授權,請前往設定頁面填寫授權碼。",
Unauthorized: "目前您的狀態是未授權,請前往設定頁面輸入授權碼。",
},
ChatItem: {
ChatItemCount: (count: number) => `${count} 條對話`,
@@ -34,11 +34,16 @@ const tw: LocaleType = {
Title: "匯出聊天記錄為 Markdown",
Copy: "複製全部",
Download: "下載檔案",
MessageFromYou: "來自你的訊息",
MessageFromChatGPT: "來自 ChatGPT 的訊息",
},
Memory: {
Title: "上下文記憶 Prompt",
EmptyContent: "尚未記憶",
Copy: "複製全部",
Send: "發送記憶",
Reset: "重置對話",
ResetConfirm: "重置後將清空當前對話記錄以及歷史記憶,確認重置?",
},
Home: {
NewChat: "新的對話",
@@ -59,6 +64,7 @@ const tw: LocaleType = {
en: "English",
tw: "繁體中文",
es: "Español",
it: "Italiano",
},
},
Avatar: "大頭貼",
@@ -98,21 +104,22 @@ const tw: LocaleType = {
},
Token: {
Title: "API Key",
SubTitle: "使用自己的 Key 可規避受控訪問限制",
SubTitle: "使用自己的 Key 可規避授權訪問限制",
Placeholder: "OpenAI API Key",
},
Usage: {
Title: "帳戶餘額",
SubTitle(used: any) {
return `本月已使用 $${used}`;
SubTitle(used: any, total: any) {
return `本月已使用 $${used},订阅总额 $${total}`;
},
IsChecking: "正在檢查…",
Check: "重新檢查",
NoAccess: "輸入API Key查看餘額",
},
AccessCode: {
Title: "訪問碼",
SubTitle: "現在是受控訪問狀態",
Placeholder: "請輸入訪問碼",
Title: "授權碼",
SubTitle: "現在是未授權訪問狀態",
Placeholder: "請輸入授權碼",
},
Model: "模型 (model)",
Temperature: {

View File

@@ -1,7 +1,5 @@
import { Analytics } from "@vercel/analytics/react";
import "array.prototype.at";
import { Home } from "./components/home";
export default function App() {

27
app/polyfill.ts Normal file
View File

@@ -0,0 +1,27 @@
declare global {
interface Array<T> {
at(index: number): T | undefined;
}
}
if (!Array.prototype.at) {
Array.prototype.at = function (index: number) {
// Get the length of the array
const length = this.length;
// Convert negative index to a positive index
if (index < 0) {
index = length + index;
}
// Return undefined if the index is out of range
if (index < 0 || index >= length) {
return undefined;
}
// Use Array.prototype.slice method to get value at the specified index
return Array.prototype.slice.call(this, index, index + 1)[0];
};
}
export {};

View File

@@ -1,6 +1,5 @@
import type { ChatRequest, ChatReponse } from "./api/openai/typing";
import { filterConfig, Message, ModelConfig, useAccessStore } from "./store";
import Locale from "./locales";
import { Message, ModelConfig, useAccessStore, useChatStore } from "./store";
import { showToast } from "./components/ui-lib";
const TIME_OUT_MS = 30000;
@@ -21,10 +20,12 @@ const makeRequestParam = (
sendMessages = sendMessages.filter((m) => m.role !== "assistant");
}
const modelConfig = useChatStore.getState().config.modelConfig;
return {
model: "gpt-3.5-turbo",
messages: sendMessages,
stream: options?.stream,
...modelConfig,
};
};
@@ -81,31 +82,39 @@ export async function requestUsage() {
const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);
const startDate = formatDate(startOfMonth);
const endDate = formatDate(now);
const res = await requestOpenaiClient(
`dashboard/billing/usage?start_date=${startDate}&end_date=${endDate}`,
)(null, "GET");
try {
const response = (await res.json()) as {
total_usage: number;
error?: {
type: string;
message: string;
};
const [used, subs] = await Promise.all([
requestOpenaiClient(
`dashboard/billing/usage?start_date=${startDate}&end_date=${endDate}`,
)(null, "GET"),
requestOpenaiClient("dashboard/billing/subscription")(null, "GET"),
]);
const response = (await used.json()) as {
total_usage?: number;
error?: {
type: string;
message: string;
};
};
if (response.error && response.error.type) {
showToast(response.error.message);
return;
}
const total = (await subs.json()) as {
hard_limit_usd?: number;
};
if (response.total_usage) {
response.total_usage = Math.round(response.total_usage) / 100;
}
return response.total_usage;
} catch (error) {
console.error("[Request usage] ", error, res.body);
if (response.error && response.error.type) {
showToast(response.error.message);
return;
}
if (response.total_usage) {
response.total_usage = Math.round(response.total_usage) / 100;
}
return {
used: response.total_usage,
subscription: total.hard_limit_usd,
};
}
export async function requestChatStream(
@@ -114,7 +123,7 @@ export async function requestChatStream(
filterBot?: boolean;
modelConfig?: ModelConfig;
onMessage: (message: string, done: boolean) => void;
onError: (error: Error) => void;
onError: (error: Error, statusCode?: number) => void;
onController?: (controller: AbortController) => void;
},
) {
@@ -123,11 +132,6 @@ export async function requestChatStream(
filterBot: options?.filterBot,
});
// valid and assign model config
if (options?.modelConfig) {
Object.assign(req, filterConfig(options.modelConfig));
}
console.log("[Request] ", req);
const controller = new AbortController();
@@ -178,11 +182,10 @@ export async function requestChatStream(
finish();
} else if (res.status === 401) {
console.error("Anauthorized");
responseText = Locale.Error.Unauthorized;
finish();
options?.onError(new Error("Anauthorized"), res.status);
} else {
console.error("Stream Error", res.body);
options?.onError(new Error("Stream Error"));
options?.onError(new Error("Stream Error"), res.status);
}
} catch (err) {
console.error("NetWork Error", err);
@@ -210,23 +213,22 @@ export const ControllerPool = {
addController(
sessionIndex: number,
messageIndex: number,
messageId: number,
controller: AbortController,
) {
const key = this.key(sessionIndex, messageIndex);
const key = this.key(sessionIndex, messageId);
this.controllers[key] = controller;
return key;
},
stop(sessionIndex: number, messageIndex: number) {
const key = this.key(sessionIndex, messageIndex);
stop(sessionIndex: number, messageId: number) {
const key = this.key(sessionIndex, messageId);
const controller = this.controllers[key];
console.log(controller);
controller?.abort();
},
remove(sessionIndex: number, messageIndex: number) {
const key = this.key(sessionIndex, messageIndex);
remove(sessionIndex: number, messageId: number) {
const key = this.key(sessionIndex, messageId);
delete this.controllers[key];
},

View File

@@ -14,8 +14,20 @@ import Locale from "../locales";
export type Message = ChatCompletionResponseMessage & {
date: string;
streaming?: boolean;
isError?: boolean;
id?: number;
};
export function createMessage(override: Partial<Message>): Message {
return {
id: Date.now(),
date: new Date().toLocaleString(),
role: "user",
content: "",
...override,
};
}
export enum SubmitKey {
Enter = "Enter",
CtrlEnter = "Ctrl + Enter",
@@ -84,43 +96,39 @@ export const ALL_MODELS = [
},
];
export function isValidModel(name: string) {
return ALL_MODELS.some((m) => m.name === name && m.available);
export function limitNumber(
x: number,
min: number,
max: number,
defaultValue: number,
) {
if (typeof x !== "number" || isNaN(x)) {
return defaultValue;
}
return Math.min(max, Math.max(min, x));
}
export function isValidNumber(x: number, min: number, max: number) {
return typeof x === "number" && x <= max && x >= min;
export function limitModel(name: string) {
return ALL_MODELS.some((m) => m.name === name && m.available)
? name
: ALL_MODELS[4].name;
}
export function filterConfig(oldConfig: ModelConfig): Partial<ModelConfig> {
const config = Object.assign({}, oldConfig);
const validator: {
[k in keyof ModelConfig]: (x: ModelConfig[keyof ModelConfig]) => boolean;
} = {
model(x) {
return isValidModel(x as string);
},
max_tokens(x) {
return isValidNumber(x as number, 100, 4000);
},
presence_penalty(x) {
return isValidNumber(x as number, -2, 2);
},
temperature(x) {
return isValidNumber(x as number, 0, 2);
},
};
Object.keys(validator).forEach((k) => {
const key = k as keyof ModelConfig;
if (!validator[key](config[key])) {
delete config[key];
}
});
return config;
}
export const ModalConfigValidator = {
model(x: string) {
return limitModel(x);
},
max_tokens(x: number) {
return limitNumber(x, 0, 32000, 2000);
},
presence_penalty(x: number) {
return limitNumber(x, -2, 2, 0);
},
temperature(x: number) {
return limitNumber(x, 0, 2, 1);
},
};
const DEFAULT_CONFIG: ChatConfig = {
historyMessageCount: 4,
@@ -152,6 +160,7 @@ export interface ChatStat {
export interface ChatSession {
id: number;
topic: string;
sendMemory: boolean;
memoryPrompt: string;
context: Message[];
messages: Message[];
@@ -161,11 +170,10 @@ export interface ChatSession {
}
const DEFAULT_TOPIC = Locale.Store.DefaultTopic;
export const BOT_HELLO: Message = {
export const BOT_HELLO: Message = createMessage({
role: "assistant",
content: Locale.Store.BotHello,
date: "",
};
});
function createEmptySession(): ChatSession {
const createDate = new Date().toLocaleString();
@@ -173,6 +181,7 @@ function createEmptySession(): ChatSession {
return {
id: Date.now(),
topic: DEFAULT_TOPIC,
sendMemory: true,
memoryPrompt: "",
context: [],
messages: [],
@@ -205,6 +214,7 @@ interface ChatStore {
messageIndex: number,
updater: (message?: Message) => void,
) => void;
resetSession: () => void;
getMessagesWithMemory: () => Message[];
getMemoryPrompt: () => Message;
@@ -311,18 +321,15 @@ export const useChatStore = create<ChatStore>()(
},
async onUserInput(content) {
const userMessage: Message = {
const userMessage: Message = createMessage({
role: "user",
content,
date: new Date().toLocaleString(),
};
});
const botMessage: Message = {
content: "",
const botMessage: Message = createMessage({
role: "assistant",
date: new Date().toLocaleString(),
streaming: true,
};
});
// get recent messages
const recentMessages = get().getMessagesWithMemory();
@@ -345,23 +352,32 @@ export const useChatStore = create<ChatStore>()(
botMessage.streaming = false;
botMessage.content = content;
get().onNewMessage(botMessage);
ControllerPool.remove(sessionIndex, messageIndex);
ControllerPool.remove(
sessionIndex,
botMessage.id ?? messageIndex,
);
} else {
botMessage.content = content;
set(() => ({}));
}
},
onError(error) {
botMessage.content += "\n\n" + Locale.Store.Error;
onError(error, statusCode) {
if (statusCode === 401) {
botMessage.content = Locale.Error.Unauthorized;
} else {
botMessage.content += "\n\n" + Locale.Store.Error;
}
botMessage.streaming = false;
userMessage.isError = true;
botMessage.isError = true;
set(() => ({}));
ControllerPool.remove(sessionIndex, messageIndex);
ControllerPool.remove(sessionIndex, botMessage.id ?? messageIndex);
},
onController(controller) {
// collect controller for stop/retry
ControllerPool.addController(
sessionIndex,
messageIndex,
botMessage.id ?? messageIndex,
controller,
);
},
@@ -383,17 +399,22 @@ export const useChatStore = create<ChatStore>()(
getMessagesWithMemory() {
const session = get().currentSession();
const config = get().config;
const n = session.messages.length;
const messages = session.messages.filter((msg) => !msg.isError);
const n = messages.length;
const context = session.context.slice();
if (session.memoryPrompt && session.memoryPrompt.length > 0) {
if (
session.sendMemory &&
session.memoryPrompt &&
session.memoryPrompt.length > 0
) {
const memoryPrompt = get().getMemoryPrompt();
context.push(memoryPrompt);
}
const recentMessages = context.concat(
session.messages.slice(Math.max(0, n - config.historyMessageCount)),
messages.slice(Math.max(0, n - config.historyMessageCount)),
);
return recentMessages;
@@ -411,6 +432,13 @@ export const useChatStore = create<ChatStore>()(
set(() => ({ sessions }));
},
resetSession() {
get().updateCurrentSession((session) => {
session.messages = [];
session.memoryPrompt = "";
});
},
summarizeSession() {
const session = get().currentSession();
@@ -423,7 +451,8 @@ export const useChatStore = create<ChatStore>()(
requestWithPrompt(session.messages, Locale.Store.Prompt.Topic).then(
(res) => {
get().updateCurrentSession(
(session) => (session.topic = trimTopic(res)),
(session) =>
(session.topic = res ? trimTopic(res) : DEFAULT_TOPIC),
);
},
);
@@ -502,7 +531,7 @@ export const useChatStore = create<ChatStore>()(
}),
{
name: LOCAL_KEY,
version: 1.1,
version: 1.2,
migrate(persistedState, version) {
const state = persistedState as ChatStore;
@@ -510,6 +539,10 @@ export const useChatStore = create<ChatStore>()(
state.sessions.forEach((s) => (s.context = []));
}
if (version < 1.2) {
state.sessions.forEach((s) => (s.sendMemory = true));
}
return state;
},
},

View File

@@ -126,6 +126,10 @@ select {
text-align: center;
}
label {
cursor: pointer;
}
input {
text-align: center;
}
@@ -184,7 +188,8 @@ input[type="range"]::-webkit-slider-thumb:hover {
}
input[type="number"],
input[type="text"] {
input[type="text"],
input[type="password"] {
appearance: none;
border-radius: 10px;
border: var(--border-in-light);
@@ -268,3 +273,18 @@ pre {
filter: brightness(0.9);
}
}
.error {
width: 80%;
border-radius: 20px;
border: var(--border-in-light);
box-shadow: var(--card-shadow);
padding: 20px;
overflow: auto;
background-color: var(--white);
color: var(--black);
pre {
overflow: auto;
}
}

View File

@@ -8,16 +8,22 @@
font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;
}
pre code.hljs {
pre code {
display: block;
overflow-x: auto;
padding: 1em;
}
code.hljs {
code {
padding: 3px 5px;
}
.hljs,
pre {
background: #1a1b26;
color: #cbd2ea;
}
/*!
Theme: Tokyo-night-Dark
origin: https://github.com/enkia/tokyo-night-vscode-theme
@@ -99,11 +105,6 @@
color: #c0caf5;
}
.hljs {
background: #1a1b26;
color: #9aa5ce;
}
.hljs-emphasis {
font-style: italic;
}

View File

@@ -1,19 +1,30 @@
import { EmojiStyle } from "emoji-picker-react";
import { showToast } from "./components/ui-lib";
import Locale from "./locales";
export function trimTopic(topic: string) {
return topic.replace(/[,。!?、,.!?]*$/, "");
return topic.replace(/[,。!?”“"、,.!?]*$/, "");
}
export function copyToClipboard(text: string) {
navigator.clipboard
.writeText(text)
.then((res) => {
showToast(Locale.Copy.Success);
})
.catch((err) => {
showToast(Locale.Copy.Failed);
export async function copyToClipboard(text: string) {
if (navigator.clipboard) {
navigator.clipboard.writeText(text).catch(err => {
console.error('Failed to copy: ', err);
});
} else {
const textArea = document.createElement('textarea');
textArea.value = text;
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
try {
document.execCommand('copy');
console.log('Text copied to clipboard');
} catch (err) {
console.error('Failed to copy: ', err);
}
document.body.removeChild(textArea);
}
}
export function downloadAs(text: string, filename: string) {
@@ -77,3 +88,7 @@ export function getCurrentVersion() {
return currentId;
}
export function getEmojiUrl(unified: string, style: EmojiStyle) {
return `https://cdn.staticfile.org/emoji-datasource-apple/14.0.0/img/${style}/64/${unified}.png`;
}

52
docs/faq-cn.md Normal file
View File

@@ -0,0 +1,52 @@
# 常见问题
> We are sorry that there is currently no English version of the FAQ. English users can use translation tools to access this document. We look forward to receiving your PR for an English version of the documentation.
<a name="change-env-vars" />
## 如何修改 Vercel 环境变量
- 进入 vercel 的控制台页面;
- 选中你的 chatgpt next web 项目;
- 点击页面头部的 Settings 选项;
- 找到侧边栏的 Environment Variables 选项;
- 修改对应的值即可。
<a name="no-stream" />
## 为什么我部署的版本没有流式响应
> 相关讨论:[#386](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/386)
如果你使用 ngnix 反向代理,需要在配置文件中增加下列代码:
```
# 不缓存,支持流式输出
proxy_cache off; # 关闭缓存
proxy_buffering off; # 关闭代理缓冲
chunked_transfer_encoding on; # 开启分块传输编码
tcp_nopush on; # 开启TCP NOPUSH选项禁止Nagle算法
tcp_nodelay on; # 开启TCP NODELAY选项禁止延迟ACK算法
keepalive_timeout 300; # 设定keep-alive超时时间为65秒
```
如果你是在 netlify 部署,此问题依然等待解决,请耐心等待。
<a name="error" />
## 为什么会一直提示“出错了,稍后重试吧”
原因可能有很多,请依次排查:
- 请先检查你的代码版本是否为最新版本,更新到最新版本后重试;
- 请检查 api key 是否设置正确,环境变量名称必须为全大写加下划线;
- 请检查 api key 是否可用;
- 如果经历了上述步骤依旧无法确定问题,请在 issue 区提交一个新 issue并附上 vercel 的 runtime log 或者 docker 运行时的 log。
<a name="docker-update" />
## 为什么 Docker 部署版本一直提示更新
Docker 版本相当于稳定版latest Docker 总是与 latest release version 一致,目前我们的发版频率是一到两天发一次,所以 Docker 版本会总是落后最新的提交一到两天,这在预期内。
<a name="random-response" />
## 为什么 ChatGPT 的回复会乱码
设置界面 - 模型设置项中,有一项为 `temperature`,如果此值大于 1那么就有可能造成回复乱码将其调回 1 以内即可。
## 如何使用 Azure OpenAI 接口
请参考:[#371](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/371)

BIN
docs/images/cover.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 376 KiB

View File

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

BIN
docs/images/more.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 278 KiB

BIN
docs/images/settings.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

View File

@@ -1,6 +1,6 @@
{
"name": "chatgpt-next-web",
"version": "1.1",
"version": "1.9.3",
"private": false,
"license": "Anti 996",
"scripts": {
@@ -14,7 +14,6 @@
"dependencies": {
"@svgr/webpack": "^6.5.1",
"@vercel/analytics": "^0.1.11",
"array.prototype.at": "^1.1.1",
"emoji-picker-react": "^4.4.7",
"eventsource-parser": "^0.1.0",
"fuse.js": "^6.6.2",

View File

@@ -61,4 +61,5 @@ read -p "Enter CODE: " CODE
read -p "Enter PORT: " PORT
# Build and run the project using the environment variables
OPENAI_API_KEY=$OPENAI_API_KEY CODE=$CODE PORT=$PORT yarn build && OPENAI_API_KEY=$OPENAI_API_KEY CODE=$CODE PORT=$PORT yarn start
OPENAI_API_KEY=$OPENAI_API_KEY CODE=$CODE PORT=$PORT yarn build
OPENAI_API_KEY=$OPENAI_API_KEY CODE=$CODE PORT=$PORT yarn start

Binary file not shown.

Before

Width:  |  Height:  |  Size: 234 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 218 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

107
yarn.lock
View File

@@ -1365,11 +1365,6 @@
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==
"@types/prismjs@^1.0.0":
version "1.26.0"
resolved "https://registry.yarnpkg.com/@types/prismjs/-/prismjs-1.26.0.tgz#a1c3809b0ad61c62cac6d4e0c56d610c910b7654"
integrity sha512-ZTaqn/qSqUuAq1YwvOFQfVW1AR/oQJlLSZVustdjwI+GZ8kr0MSHBj0tsXPW1EqHubx50gtBEjbPGsdZwQwCjQ==
"@types/prop-types@*", "@types/prop-types@^15.0.0":
version "15.7.5"
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf"
@@ -1570,16 +1565,6 @@ array-union@^2.1.0:
resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d"
integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==
array.prototype.at@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/array.prototype.at/-/array.prototype.at-1.1.1.tgz#6deda3cd3c704afa16361387ea344e0b8d8831b5"
integrity sha512-n/wYNLJy/fVEU9EGPt2ww920hy1XX3XB2yTREFy1QsxctBgQV/tZIwg1G8jVxELna4pLCzg/xvvS/DDXtI4NNg==
dependencies:
call-bind "^1.0.2"
define-properties "^1.1.4"
es-abstract "^1.20.4"
es-shim-unscopables "^1.0.0"
array.prototype.flat@^1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz#ffc6576a7ca3efc2f46a143b9d1dda9b4b3cf5e2"
@@ -1769,21 +1754,11 @@ chalk@^4.0.0:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
character-entities-legacy@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz#76bc83a90738901d7bc223a9e93759fdd560125b"
integrity sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==
character-entities@^2.0.0:
version "2.0.2"
resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-2.0.2.tgz#2d09c2e72cd9523076ccb21157dff66ad43fcc22"
integrity sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==
character-reference-invalid@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz#85c66b041e43b47210faf401278abf808ac45cb9"
integrity sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==
"chokidar@>=3.0.0 <4.0.0":
version "3.5.3"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd"
@@ -2879,13 +2854,6 @@ hast-util-parse-selector@^3.0.0:
dependencies:
"@types/hast" "^2.0.0"
hast-util-to-string@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/hast-util-to-string/-/hast-util-to-string-2.0.0.tgz#b008b0a4ea472bf34dd390b7eea1018726ae152a"
integrity sha512-02AQ3vLhuH3FisaMM+i/9sm4OXGSq1UhOOCpTLLQtHdL3tZt7qil69r8M8iDkZYyC0HCFylcYoP+8IO7ddta1A==
dependencies:
"@types/hast" "^2.0.0"
hast-util-to-text@^3.0.0, hast-util-to-text@^3.1.0:
version "3.1.2"
resolved "https://registry.yarnpkg.com/hast-util-to-text/-/hast-util-to-text-3.1.2.tgz#ecf30c47141f41e91a5d32d0b1e1859fd2ac04f2"
@@ -2982,19 +2950,6 @@ internal-slot@^1.0.3, internal-slot@^1.0.4, internal-slot@^1.0.5:
has "^1.0.3"
side-channel "^1.0.4"
is-alphabetical@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/is-alphabetical/-/is-alphabetical-2.0.1.tgz#01072053ea7c1036df3c7d19a6daaec7f19e789b"
integrity sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==
is-alphanumerical@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz#7c03fbe96e3e931113e57f964b0a368cc2dfd875"
integrity sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==
dependencies:
is-alphabetical "^2.0.0"
is-decimal "^2.0.0"
is-arguments@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b"
@@ -3063,11 +3018,6 @@ is-date-object@^1.0.1, is-date-object@^1.0.5:
dependencies:
has-tostringtag "^1.0.0"
is-decimal@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-2.0.1.tgz#9469d2dc190d0214fd87d78b78caecc0cc14eef7"
integrity sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==
is-docker@^2.0.0, is-docker@^2.1.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa"
@@ -3095,11 +3045,6 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1:
dependencies:
is-extglob "^2.1.1"
is-hexadecimal@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz#86b5bf668fca307498d319dfc03289d781a90027"
integrity sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==
is-map@^2.0.1, is-map@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.2.tgz#00922db8c9bf73e81b7a335827bc2a43f2b91127"
@@ -4156,20 +4101,6 @@ parent-module@^1.0.0:
dependencies:
callsites "^3.0.0"
parse-entities@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-4.0.1.tgz#4e2a01111fb1c986549b944af39eeda258fc9e4e"
integrity sha512-SWzvYcSJh4d/SGLIOQfZ/CoNv6BTlI6YEQ7Nj82oDVnRpwe/Z/F1EMx42x3JAOwGBlCjeCH0BRJQbQ/opHL17w==
dependencies:
"@types/unist" "^2.0.0"
character-entities "^2.0.0"
character-entities-legacy "^3.0.0"
character-reference-invalid "^2.0.0"
decode-named-character-reference "^1.0.0"
is-alphanumerical "^2.0.0"
is-decimal "^2.0.0"
is-hexadecimal "^2.0.0"
parse-json@^5.0.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd"
@@ -4180,11 +4111,6 @@ parse-json@^5.0.0:
json-parse-even-better-errors "^2.3.0"
lines-and-columns "^1.1.6"
parse-numeric-range@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/parse-numeric-range/-/parse-numeric-range-1.3.0.tgz#7c63b61190d61e4d53a1197f0c83c47bb670ffa3"
integrity sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==
parse5@^6.0.0:
version "6.0.1"
resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b"
@@ -4338,16 +4264,6 @@ readdirp@~3.6.0:
dependencies:
picomatch "^2.2.1"
refractor@^4.7.0:
version "4.8.1"
resolved "https://registry.yarnpkg.com/refractor/-/refractor-4.8.1.tgz#fbdd889333a3d86c9c864479622855c9b38e9d42"
integrity sha512-/fk5sI0iTgFYlmVGYVew90AoYnNMP6pooClx/XKqyeeCQXrL0Kvgn8V0VEht5ccdljbzzF1i3Q213gcntkRExg==
dependencies:
"@types/hast" "^2.0.0"
"@types/prismjs" "^1.0.0"
hastscript "^7.0.0"
parse-entities "^4.0.0"
regenerate-unicode-properties@^10.1.0:
version "10.1.0"
resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz#7c3192cab6dd24e21cb4461e5ddd7dd24fa8374c"
@@ -4425,7 +4341,7 @@ rehype-katex@^6.0.2:
unist-util-remove-position "^4.0.0"
unist-util-visit "^4.0.0"
rehype-parse@^8.0.0, rehype-parse@^8.0.2:
rehype-parse@^8.0.0:
version "8.0.4"
resolved "https://registry.yarnpkg.com/rehype-parse/-/rehype-parse-8.0.4.tgz#3d17c9ff16ddfef6bbcc8e6a25a99467b482d688"
integrity sha512-MJJKONunHjoTh4kc3dsM1v3C9kGrrxvA3U8PxZlP2SjH8RNUSrb+lF7Y0KVaUDnGH2QZ5vAn7ulkiajM9ifuqg==
@@ -4435,18 +4351,6 @@ rehype-parse@^8.0.0, rehype-parse@^8.0.2:
parse5 "^6.0.0"
unified "^10.0.0"
rehype-prism-plus@^1.5.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/rehype-prism-plus/-/rehype-prism-plus-1.5.1.tgz#b5f4eb3c789a13ffe874c81039665e144bcb1cae"
integrity sha512-mowYefSfrIkMMxkb0fwuEXlvc5nA9b1vQ6mzujM81Qx28RI0mo7jCHsBZ2tJ4eIJKXdFn+EdPkZZBGB10K02vg==
dependencies:
hast-util-to-string "^2.0.0"
parse-numeric-range "^1.3.0"
refractor "^4.7.0"
rehype-parse "^8.0.2"
unist-util-filter "^4.0.0"
unist-util-visit "^4.0.0"
remark-breaks@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/remark-breaks/-/remark-breaks-3.0.2.tgz#f466b9d3474d7323146c0149fc1496dabadd908e"
@@ -4996,15 +4900,6 @@ unified@^10.0.0:
trough "^2.0.0"
vfile "^5.0.0"
unist-util-filter@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/unist-util-filter/-/unist-util-filter-4.0.1.tgz#fd885dd48adaad345de5f5dc706ec4ff44a8d074"
integrity sha512-RynicUM/vbOSTSiUK+BnaK9XMfmQUh6gyi7L6taNgc7FIf84GukXVV3ucGzEN/PhUUkdP5hb1MmXc+3cvPUm5Q==
dependencies:
"@types/unist" "^2.0.0"
unist-util-is "^5.0.0"
unist-util-visit-parents "^5.0.0"
unist-util-find-after@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/unist-util-find-after/-/unist-util-find-after-4.0.1.tgz#80c69c92b0504033638ce11973f4135f2c822e2d"