Compare commits
172 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
cae4655785 | ||
|
28c12606a4 | ||
|
54df355014 | ||
|
cf50299b14 | ||
|
2c12be62c4 | ||
|
4636a75b5e | ||
|
03756e364a | ||
|
ce1715c79e | ||
|
a62ab3c649 | ||
|
bfb7b988f4 | ||
|
84f41262f5 | ||
|
dda40e29f4 | ||
|
7df868e22a | ||
|
bf5e7aaa48 | ||
|
d76e744eab | ||
|
6f5699fe09 | ||
|
f9d916925e | ||
|
f9258878db | ||
|
ef9e86b50d | ||
|
b21931c667 | ||
|
06de3f5e69 | ||
|
261a8fd83b | ||
|
6527074cde | ||
|
f2485931d9 | ||
|
4f8a0b7711 | ||
|
2dde55050e | ||
|
6aade62ce2 | ||
|
45b88ebb2a | ||
|
dc7159a450 | ||
|
536ace8e10 | ||
|
16b2a3e66e | ||
|
6f135a0cce | ||
|
cf220dd2eb | ||
|
914f4fb862 | ||
|
7bdb68eecf | ||
|
3c510cfaf0 | ||
|
401fa198c9 | ||
|
600df4f63a | ||
|
74eb42c111 | ||
|
9876a1aeca | ||
|
d898ffce23 | ||
|
f1772f4625 | ||
|
9da455a7ea | ||
|
2bd6342309 | ||
|
5e73577088 | ||
|
5156a80738 | ||
|
a9d605ed30 | ||
|
7d1fae32cd | ||
|
9f17b45bc4 | ||
|
a64c9dae42 | ||
|
5fbf4c394c | ||
|
1e5153173c | ||
|
011b52d07d | ||
|
d033168d80 | ||
|
fdca9e59de | ||
|
549a2fd206 | ||
|
a0cd939bfd | ||
|
4f52679ec6 | ||
|
b52e237044 | ||
|
17b329bb63 | ||
|
3a654ba199 | ||
|
5ba3fc9321 | ||
|
a46f08154e | ||
|
0f6ed9c293 | ||
|
295864d36b | ||
|
be6d45e49f | ||
|
22b6987249 | ||
|
64647b0bb3 | ||
|
a5a1f2e8ad | ||
|
8bd39f33e0 | ||
|
be9774943b | ||
|
943214c6a7 | ||
|
ca792669fc | ||
|
78d68a9949 | ||
|
6b2db97347 | ||
|
2bfb362832 | ||
|
cb140e482f | ||
|
e6b72ac1ff | ||
|
8032e6d68d | ||
|
c7e0a6f37f | ||
|
1141cd2e6e | ||
|
c9dd953817 | ||
|
b7ffca031e | ||
|
fd2f441e02 | ||
|
3b3ebda34b | ||
|
87e3d663a2 | ||
|
d0a1d910d4 | ||
|
33b97082fa | ||
|
d93f05f511 | ||
|
3dc29197e1 | ||
|
fbc0236748 | ||
|
1304a3943a | ||
|
8c0ba1aee2 | ||
|
47154773f2 | ||
|
deca9bf06d | ||
|
6ab4c9be2e | ||
|
3a519612a0 | ||
|
d1ec26ae83 | ||
|
9cb889c34f | ||
|
77f27b7948 | ||
|
dbee5bc515 | ||
|
54a5332834 | ||
|
8af164c99f | ||
|
c7321fddfb | ||
|
c5ca278253 | ||
|
638fdd8c3e | ||
|
3407b57a51 | ||
|
836bf836d3 | ||
|
6e52d14180 | ||
|
1096fe55e1 | ||
|
d2d615c84a | ||
|
9f26c8cecb | ||
|
cd1f082c52 | ||
|
b90dfb48ee | ||
|
70e67a686b | ||
|
d1c3d900ab | ||
|
e8a4ad1001 | ||
|
ccac85b079 | ||
|
f92fefb6cd | ||
|
cc565cf57e | ||
|
4a56e9c884 | ||
|
d11d906fa2 | ||
|
65c4a0c319 | ||
|
55bcf78efe | ||
|
f5a2ce52aa | ||
|
0dcbfd746e | ||
|
06e043d3f1 | ||
|
986d34fb3e | ||
|
5296ab1b09 | ||
|
b5d3348d99 | ||
|
1e77df381a | ||
|
ec33281ff5 | ||
|
ba67f13ad5 | ||
|
1604ed91fb | ||
|
74fcaab5e9 | ||
|
adee435ea6 | ||
|
ea59ab5176 | ||
|
f78008c1b2 | ||
|
f54db695af | ||
|
f21f922160 | ||
|
fd413c7b52 | ||
|
3e2c5af4b5 | ||
|
bdb49b1171 | ||
|
5933b3d7eb | ||
|
4c8d606fae | ||
|
13c1d2fd2b | ||
|
e35c807216 | ||
|
5a2cc6f154 | ||
|
88f8c43472 | ||
|
ef3e8e6fac | ||
|
1505372e20 | ||
|
ad5093ce05 | ||
|
b558d1afc6 | ||
|
ddfd05b008 | ||
|
d2ad01a9ff | ||
|
64a17abfe2 | ||
|
04b638aa06 | ||
|
31e30906d0 | ||
|
bc00be9065 | ||
|
4a599e986f | ||
|
f1ca03e378 | ||
|
f3d5fc7a84 | ||
|
3bfcdf9c41 | ||
|
144200e315 | ||
|
398e229c77 | ||
|
6a61fe5776 | ||
|
9835206452 | ||
|
70b0580fb7 | ||
|
23eb7732d7 | ||
|
26e50cefea | ||
|
588e907181 | ||
|
eae7d6260f |
@@ -17,11 +17,6 @@ BASE_URL=
|
|||||||
# Default: Empty
|
# Default: Empty
|
||||||
OPENAI_ORG_ID=
|
OPENAI_ORG_ID=
|
||||||
|
|
||||||
# (optional)
|
|
||||||
# Default: Empty
|
|
||||||
# If you do not want users to input their own API key, set this value to 1.
|
|
||||||
HIDE_USER_API_KEY=
|
|
||||||
|
|
||||||
# (optional)
|
# (optional)
|
||||||
# Default: Empty
|
# Default: Empty
|
||||||
# If you do not want users to use GPT-4, set this value to 1.
|
# If you do not want users to use GPT-4, set this value to 1.
|
||||||
@@ -29,5 +24,15 @@ DISABLE_GPT4=
|
|||||||
|
|
||||||
# (optional)
|
# (optional)
|
||||||
# Default: Empty
|
# Default: Empty
|
||||||
# If you do not want users to query balance, set this value to 1.
|
# If you do not want users to input their own API key, set this value to 1.
|
||||||
HIDE_BALANCE_QUERY=
|
HIDE_USER_API_KEY=
|
||||||
|
|
||||||
|
# (optional)
|
||||||
|
# Default: Empty
|
||||||
|
# If you do want users to query balance, set this value to 1.
|
||||||
|
ENABLE_BALANCE_QUERY=
|
||||||
|
|
||||||
|
# (optional)
|
||||||
|
# Default: Empty
|
||||||
|
# If you want to disable parse settings from url, set this value to 1.
|
||||||
|
DISABLE_FAST_LINK=
|
||||||
|
4
.github/workflows/app.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
|||||||
- name: setup node
|
- name: setup node
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 16
|
node-version: 18
|
||||||
- name: get version
|
- name: get version
|
||||||
run: echo "PACKAGE_VERSION=$(node -p "require('./src-tauri/tauri.conf.json').package.version")" >> $GITHUB_ENV
|
run: echo "PACKAGE_VERSION=$(node -p "require('./src-tauri/tauri.conf.json').package.version")" >> $GITHUB_ENV
|
||||||
- name: create release
|
- name: create release
|
||||||
@@ -59,7 +59,7 @@ jobs:
|
|||||||
- name: setup node
|
- name: setup node
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 16
|
node-version: 18
|
||||||
- name: install Rust stable
|
- name: install Rust stable
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
with:
|
with:
|
||||||
|
58
README.md
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
<h1 align="center">ChatGPT Next Web</h1>
|
<h1 align="center">ChatGPT Next Web</h1>
|
||||||
|
|
||||||
English / [简体中文](./README_CN.md) / [日本語](./README_JA.md)
|
English / [简体中文](./README_CN.md)
|
||||||
|
|
||||||
One-Click to get well-designed cross-platform ChatGPT web UI.
|
One-Click to get well-designed cross-platform ChatGPT web UI.
|
||||||
|
|
||||||
@@ -62,6 +62,7 @@ One-Click to get well-designed cross-platform ChatGPT web UI.
|
|||||||
- 🚀 v2.0 is released, now you can create prompt templates, turn your ideas into reality! Read this: [ChatGPT Prompt Engineering Tips: Zero, One and Few Shot Prompting](https://www.allabtai.com/prompt-engineering-tips-zero-one-and-few-shot-prompting/).
|
- 🚀 v2.0 is released, now you can create prompt templates, turn your ideas into reality! Read this: [ChatGPT Prompt Engineering Tips: Zero, One and Few Shot Prompting](https://www.allabtai.com/prompt-engineering-tips-zero-one-and-few-shot-prompting/).
|
||||||
- 🚀 v2.7 let's share conversations as image, or share to ShareGPT!
|
- 🚀 v2.7 let's share conversations as image, or share to ShareGPT!
|
||||||
- 🚀 v2.8 now we have a client that runs across all platforms!
|
- 🚀 v2.8 now we have a client that runs across all platforms!
|
||||||
|
- 🚀 v2.9.11 you can use azure endpoint now.
|
||||||
|
|
||||||
## 主要功能
|
## 主要功能
|
||||||
|
|
||||||
@@ -74,7 +75,7 @@ One-Click to get well-designed cross-platform ChatGPT web UI.
|
|||||||
- 预制角色功能(面具),方便地创建、分享和调试你的个性化对话
|
- 预制角色功能(面具),方便地创建、分享和调试你的个性化对话
|
||||||
- 海量的内置 prompt 列表,来自[中文](https://github.com/PlexPt/awesome-chatgpt-prompts-zh)和[英文](https://github.com/f/awesome-chatgpt-prompts)
|
- 海量的内置 prompt 列表,来自[中文](https://github.com/PlexPt/awesome-chatgpt-prompts-zh)和[英文](https://github.com/f/awesome-chatgpt-prompts)
|
||||||
- 自动压缩上下文聊天记录,在节省 Token 的同时支持超长对话
|
- 自动压缩上下文聊天记录,在节省 Token 的同时支持超长对话
|
||||||
- 多国语言支持:English, 简体中文, 繁体中文, 日本語, Español, Italiano, Türkçe, Deutsch, Tiếng Việt, Русский, Čeština
|
- 多国语言支持:English, 简体中文, 繁体中文, 日本語, Español, Italiano, Türkçe, Deutsch, Tiếng Việt, Русский, Čeština, 한국어, Indonesia
|
||||||
- 拥有自己的域名?好上加好,绑定后即可在任何地方**无障碍**快速访问
|
- 拥有自己的域名?好上加好,绑定后即可在任何地方**无障碍**快速访问
|
||||||
|
|
||||||
## 开发计划
|
## 开发计划
|
||||||
@@ -93,6 +94,7 @@ One-Click to get well-designed cross-platform ChatGPT web UI.
|
|||||||
- 💡 想要更方便地随时随地使用本项目?可以试下这款桌面插件:https://github.com/mushan0x0/AI0x0.com
|
- 💡 想要更方便地随时随地使用本项目?可以试下这款桌面插件:https://github.com/mushan0x0/AI0x0.com
|
||||||
- 🚀 v2.7 现在可以将会话分享为图片了,也可以分享到 ShareGPT 的在线链接。
|
- 🚀 v2.7 现在可以将会话分享为图片了,也可以分享到 ShareGPT 的在线链接。
|
||||||
- 🚀 v2.8 发布了横跨 Linux/Windows/MacOS 的体积极小的客户端。
|
- 🚀 v2.8 发布了横跨 Linux/Windows/MacOS 的体积极小的客户端。
|
||||||
|
- 🚀 v2.9.11 现在可以使用自定义 Azure 服务了。
|
||||||
|
|
||||||
## Get Started
|
## Get Started
|
||||||
|
|
||||||
@@ -153,13 +155,13 @@ After adding or modifying this environment variable, please redeploy the project
|
|||||||
|
|
||||||
> [简体中文 > 如何配置 api key、访问密码、接口代理](./README_CN.md#环境变量)
|
> [简体中文 > 如何配置 api key、访问密码、接口代理](./README_CN.md#环境变量)
|
||||||
|
|
||||||
### `OPENAI_API_KEY` (required)
|
|
||||||
|
|
||||||
Your openai api key.
|
|
||||||
|
|
||||||
### `CODE` (optional)
|
### `CODE` (optional)
|
||||||
|
|
||||||
Access passsword, separated by comma.
|
Access password, separated by comma.
|
||||||
|
|
||||||
|
### `OPENAI_API_KEY` (required)
|
||||||
|
|
||||||
|
Your openai api key, join multiple api keys with comma.
|
||||||
|
|
||||||
### `BASE_URL` (optional)
|
### `BASE_URL` (optional)
|
||||||
|
|
||||||
@@ -173,6 +175,20 @@ Override openai api request base url.
|
|||||||
|
|
||||||
Specify OpenAI organization ID.
|
Specify OpenAI organization ID.
|
||||||
|
|
||||||
|
### `AZURE_URL` (optional)
|
||||||
|
|
||||||
|
> Example: https://{azure-resource-url}/openai/deployments/{deploy-name}
|
||||||
|
|
||||||
|
Azure deploy url.
|
||||||
|
|
||||||
|
### `AZURE_API_KEY` (optional)
|
||||||
|
|
||||||
|
Azure Api Key.
|
||||||
|
|
||||||
|
### `AZURE_API_VERSION` (optional)
|
||||||
|
|
||||||
|
Azure Api Version, find it at [Azure Documentation](https://learn.microsoft.com/en-us/azure/ai-services/openai/reference#chat-completions).
|
||||||
|
|
||||||
### `HIDE_USER_API_KEY` (optional)
|
### `HIDE_USER_API_KEY` (optional)
|
||||||
|
|
||||||
> Default: Empty
|
> Default: Empty
|
||||||
@@ -185,11 +201,26 @@ If you do not want users to input their own API key, set this value to 1.
|
|||||||
|
|
||||||
If you do not want users to use GPT-4, set this value to 1.
|
If you do not want users to use GPT-4, set this value to 1.
|
||||||
|
|
||||||
### `HIDE_BALANCE_QUERY` (optional)
|
### `ENABLE_BALANCE_QUERY` (optional)
|
||||||
|
|
||||||
> Default: Empty
|
> Default: Empty
|
||||||
|
|
||||||
If you do not want users to query balance, set this value to 1.
|
If you do want users to query balance, set this value to 1, or you should set it to 0.
|
||||||
|
|
||||||
|
### `DISABLE_FAST_LINK` (optional)
|
||||||
|
|
||||||
|
> Default: Empty
|
||||||
|
|
||||||
|
If you want to disable parse settings from url, set this to 1.
|
||||||
|
|
||||||
|
### `CUSTOM_MODELS` (optional)
|
||||||
|
|
||||||
|
> Default: Empty
|
||||||
|
> Example: `+llama,+claude-2,-gpt-3.5-turbo,gpt-4-1106-preview=gpt-4-turbo` means add `llama, claude-2` to model list, and remove `gpt-3.5-turbo` from list, and display `gpt-4-1106-preview` as `gpt-4-turbo`.
|
||||||
|
|
||||||
|
To control custom models, use `+` to add a custom model, use `-` to hide a model, use `name=displayName` to customize model name, separated by comma.
|
||||||
|
|
||||||
|
User `-all` to disable all default models, `+all` to enable all default models.
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
@@ -257,6 +288,10 @@ If your proxy needs password, use:
|
|||||||
bash <(curl -s https://raw.githubusercontent.com/Yidadaa/ChatGPT-Next-Web/main/scripts/setup.sh)
|
bash <(curl -s https://raw.githubusercontent.com/Yidadaa/ChatGPT-Next-Web/main/scripts/setup.sh)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Synchronizing Chat Records (UpStash)
|
||||||
|
|
||||||
|
| [简体中文](./docs/synchronise-chat-logs-cn.md) | [English](./docs/synchronise-chat-logs-en.md) | [Italiano](./docs/synchronise-chat-logs-es.md) | [日本語](./docs/synchronise-chat-logs-ja.md) | [한국어](./docs/synchronise-chat-logs-ko.md)
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
> Please go to the [docs][./docs] directory for more documentation instructions.
|
> Please go to the [docs][./docs] directory for more documentation instructions.
|
||||||
@@ -309,6 +344,11 @@ If you want to add a new translation, read this [document](./docs/translation.md
|
|||||||
[@AnsonHyq](https://github.com/AnsonHyq)
|
[@AnsonHyq](https://github.com/AnsonHyq)
|
||||||
[@synwith](https://github.com/synwith)
|
[@synwith](https://github.com/synwith)
|
||||||
[@piksonGit](https://github.com/piksonGit)
|
[@piksonGit](https://github.com/piksonGit)
|
||||||
|
[@ouyangzhiping](https://github.com/ouyangzhiping)
|
||||||
|
[@wenjiavv](https://github.com/wenjiavv)
|
||||||
|
[@LeXwDeX](https://github.com/LeXwDeX)
|
||||||
|
[@Licoy](https://github.com/Licoy)
|
||||||
|
[@shangmin2009](https://github.com/shangmin2009)
|
||||||
|
|
||||||
### Contributor
|
### Contributor
|
||||||
|
|
||||||
|
34
README_CN.md
@@ -68,7 +68,7 @@ code1,code2,code3
|
|||||||
|
|
||||||
### `OPENAI_API_KEY` (必填项)
|
### `OPENAI_API_KEY` (必填项)
|
||||||
|
|
||||||
OpanAI 密钥,你在 openai 账户页面申请的 api key。
|
OpanAI 密钥,你在 openai 账户页面申请的 api key,使用英文逗号隔开多个 key,这样可以随机轮询这些 key。
|
||||||
|
|
||||||
### `CODE` (可选)
|
### `CODE` (可选)
|
||||||
|
|
||||||
@@ -90,6 +90,20 @@ OpenAI 接口代理 URL,如果你手动配置了 openai 接口代理,请填
|
|||||||
|
|
||||||
指定 OpenAI 中的组织 ID。
|
指定 OpenAI 中的组织 ID。
|
||||||
|
|
||||||
|
### `AZURE_URL` (可选)
|
||||||
|
|
||||||
|
> 形如:https://{azure-resource-url}/openai/deployments/{deploy-name}
|
||||||
|
|
||||||
|
Azure 部署地址。
|
||||||
|
|
||||||
|
### `AZURE_API_KEY` (可选)
|
||||||
|
|
||||||
|
Azure 密钥。
|
||||||
|
|
||||||
|
### `AZURE_API_VERSION` (可选)
|
||||||
|
|
||||||
|
Azure Api 版本,你可以在这里找到:[Azure 文档](https://learn.microsoft.com/en-us/azure/ai-services/openai/reference#chat-completions)。
|
||||||
|
|
||||||
### `HIDE_USER_API_KEY` (可选)
|
### `HIDE_USER_API_KEY` (可选)
|
||||||
|
|
||||||
如果你不想让用户自行填入 API Key,将此环境变量设置为 1 即可。
|
如果你不想让用户自行填入 API Key,将此环境变量设置为 1 即可。
|
||||||
@@ -98,9 +112,20 @@ OpenAI 接口代理 URL,如果你手动配置了 openai 接口代理,请填
|
|||||||
|
|
||||||
如果你不想让用户使用 GPT-4,将此环境变量设置为 1 即可。
|
如果你不想让用户使用 GPT-4,将此环境变量设置为 1 即可。
|
||||||
|
|
||||||
### `HIDE_BALANCE_QUERY` (可选)
|
### `ENABLE_BALANCE_QUERY` (可选)
|
||||||
|
|
||||||
如果你不想让用户查询余额,将此环境变量设置为 1 即可。
|
如果你想启用余额查询功能,将此环境变量设置为 1 即可。
|
||||||
|
|
||||||
|
### `DISABLE_FAST_LINK` (可选)
|
||||||
|
|
||||||
|
如果你想禁用从链接解析预制设置,将此环境变量设置为 1 即可。
|
||||||
|
|
||||||
|
### `CUSTOM_MODELS` (可选)
|
||||||
|
|
||||||
|
> 示例:`+qwen-7b-chat,+glm-6b,-gpt-3.5-turbo,gpt-4-1106-preview=gpt-4-turbo` 表示增加 `qwen-7b-chat` 和 `glm-6b` 到模型列表,而从列表中删除 `gpt-3.5-turbo`,并将 `gpt-4-1106-preview` 模型名字展示为 `gpt-4-turbo`。
|
||||||
|
> 如果你想先禁用所有模型,再启用指定模型,可以使用 `-all,+gpt-3.5-turbo`,则表示仅启用 `gpt-3.5-turbo`
|
||||||
|
|
||||||
|
用来控制模型列表,使用 `+` 增加一个模型,使用 `-` 来隐藏一个模型,使用 `模型名=展示名` 来自定义模型的展示名,用英文逗号隔开。
|
||||||
|
|
||||||
## 开发
|
## 开发
|
||||||
|
|
||||||
@@ -114,7 +139,7 @@ OpenAI 接口代理 URL,如果你手动配置了 openai 接口代理,请填
|
|||||||
OPENAI_API_KEY=<your api key here>
|
OPENAI_API_KEY=<your api key here>
|
||||||
|
|
||||||
# 中国大陆用户,可以使用本项目自带的代理进行开发,你也可以自由选择其他代理地址
|
# 中国大陆用户,可以使用本项目自带的代理进行开发,你也可以自由选择其他代理地址
|
||||||
BASE_URL=https://nb.nextweb.fun/api/proxy
|
BASE_URL=https://b.nextweb.fun/api/proxy
|
||||||
```
|
```
|
||||||
|
|
||||||
### 本地开发
|
### 本地开发
|
||||||
@@ -180,6 +205,7 @@ bash <(curl -s https://raw.githubusercontent.com/Yidadaa/ChatGPT-Next-Web/main/s
|
|||||||
[见项目贡献者列表](https://github.com/Yidadaa/ChatGPT-Next-Web/graphs/contributors)
|
[见项目贡献者列表](https://github.com/Yidadaa/ChatGPT-Next-Web/graphs/contributors)
|
||||||
|
|
||||||
### 相关项目
|
### 相关项目
|
||||||
|
|
||||||
- [one-api](https://github.com/songquanpeng/one-api): 一站式大模型额度管理平台,支持市面上所有主流大语言模型
|
- [one-api](https://github.com/songquanpeng/one-api): 一站式大模型额度管理平台,支持市面上所有主流大语言模型
|
||||||
|
|
||||||
## 开源协议
|
## 开源协议
|
||||||
|
173
README_ES.md
@@ -1,173 +0,0 @@
|
|||||||
<div align="center">
|
|
||||||
<img src="./docs/images/icon.svg" alt="预览"/>
|
|
||||||
|
|
||||||
<h1 align="center">ChatGPT Next Web</h1>
|
|
||||||
|
|
||||||
Implemente su aplicación web privada ChatGPT de forma gratuita con un solo clic.
|
|
||||||
|
|
||||||
[Demo demo](https://chat-gpt-next-web.vercel.app/) / [Problemas de comentarios](https://github.com/Yidadaa/ChatGPT-Next-Web/issues) / [Únete a Discord](https://discord.gg/zrhvHCr79N) / [Grupo QQ](https://user-images.githubusercontent.com/16968934/228190818-7dd00845-e9b9-4363-97e5-44c507ac76da.jpeg) / [Desarrolladores de consejos](https://user-images.githubusercontent.com/16968934/227772541-5bcd52d8-61b7-488c-a203-0330d8006e2b.jpg) / [Donar](#捐赠-donate-usdt)
|
|
||||||
|
|
||||||
[](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)
|
|
||||||
|
|
||||||
[](https://gitpod.io/#https://github.com/Yidadaa/ChatGPT-Next-Web)
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
## Comenzar
|
|
||||||
|
|
||||||
1. Prepara el tuyo [Clave API OpenAI](https://platform.openai.com/account/api-keys);
|
|
||||||
2. Haga clic en el botón de la derecha para iniciar la implementación:
|
|
||||||
[](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), inicie sesión directamente con su cuenta de Github y recuerde completar la clave API y la suma en la página de variables de entorno[Contraseña de acceso a la página](#配置页面访问密码) CÓDIGO;
|
|
||||||
3. Una vez implementado, puede comenzar;
|
|
||||||
4. (Opcional)[Enlazar un nombre de dominio personalizado](https://vercel.com/docs/concepts/projects/domains/add-a-domain): El nombre de dominio DNS asignado por Vercel está contaminado en algunas regiones y puede conectarse directamente enlazando un nombre de dominio personalizado.
|
|
||||||
|
|
||||||
## Manténgase actualizado
|
|
||||||
|
|
||||||
Si sigue los pasos anteriores para implementar su proyecto con un solo clic, es posible que siempre diga "La actualización existe" porque Vercel creará un nuevo proyecto para usted de forma predeterminada en lugar de bifurcar el proyecto, lo que evitará que la actualización se detecte correctamente.
|
|
||||||
Le recomendamos que siga estos pasos para volver a implementar:
|
|
||||||
|
|
||||||
- Eliminar el repositorio original;
|
|
||||||
- Utilice el botón de bifurcación en la esquina superior derecha de la página para bifurcar este proyecto;
|
|
||||||
- En Vercel, vuelva a seleccionar e implementar,[Echa un vistazo al tutorial detallado](./docs/vercel-cn.md#如何新建项目)。
|
|
||||||
|
|
||||||
### Activar actualizaciones automáticas
|
|
||||||
|
|
||||||
> Si encuentra un error de ejecución de Upstream Sync, ¡Sync Fork manualmente una vez!
|
|
||||||
|
|
||||||
Cuando bifurca el proyecto, debido a las limitaciones de Github, debe ir manualmente a la página Acciones de su proyecto bifurcado para habilitar Flujos de trabajo y habilitar Upstream Sync Action, después de habilitarlo, puede activar las actualizaciones automáticas cada hora:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
### Actualizar el código manualmente
|
|
||||||
|
|
||||||
Si desea que el manual se actualice inmediatamente, puede consultarlo [Documentación para Github](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork) Aprenda a sincronizar un proyecto bifurcado con código ascendente.
|
|
||||||
|
|
||||||
Puede destacar / ver este proyecto o seguir al autor para recibir notificaciones de nuevas actualizaciones de funciones.
|
|
||||||
|
|
||||||
## Configurar la contraseña de acceso a la página
|
|
||||||
|
|
||||||
> Después de configurar la contraseña, el usuario debe completar manualmente el código de acceso en la página de configuración para chatear normalmente, de lo contrario, se solicitará el estado no autorizado a través de un mensaje.
|
|
||||||
|
|
||||||
> **advertir**: Asegúrese de establecer el número de dígitos de la contraseña lo suficientemente largo, preferiblemente más de 7 dígitos, de lo contrario[Será volado](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/518)。
|
|
||||||
|
|
||||||
Este proyecto proporciona control de permisos limitado, agregue el nombre al nombre en la página Variables de entorno del Panel de control del proyecto Vercel `CODE` Variables de entorno con valores para contraseñas personalizadas separadas por comas:
|
|
||||||
|
|
||||||
code1,code2,code3
|
|
||||||
|
|
||||||
Después de agregar o modificar la variable de entorno, por favor**Redesplegar**proyecto para poner en vigor los cambios.
|
|
||||||
|
|
||||||
## Variable de entorno
|
|
||||||
|
|
||||||
> La mayoría de los elementos de configuración de este proyecto se establecen a través de variables de entorno, tutorial:[Cómo modificar las variables de entorno de Vercel](./docs/vercel-cn.md)。
|
|
||||||
|
|
||||||
### `OPENAI_API_KEY` (Requerido)
|
|
||||||
|
|
||||||
OpanAI key, la clave API que solicita en la página de su cuenta openai.
|
|
||||||
|
|
||||||
### `CODE` (Opcional)
|
|
||||||
|
|
||||||
Las contraseñas de acceso, opcionalmente, se pueden separar por comas.
|
|
||||||
|
|
||||||
**advertir**: Si no completa este campo, cualquiera puede usar directamente su sitio web implementado, lo que puede hacer que su token se consuma rápidamente, se recomienda completar esta opción.
|
|
||||||
|
|
||||||
### `BASE_URL` (Opcional)
|
|
||||||
|
|
||||||
> Predeterminado: `https://api.openai.com`
|
|
||||||
|
|
||||||
> Ejemplos: `http://your-openai-proxy.com`
|
|
||||||
|
|
||||||
URL del proxy de interfaz OpenAI, complete esta opción si configuró manualmente el proxy de interfaz openAI.
|
|
||||||
|
|
||||||
> Si encuentra problemas con el certificado SSL, establezca el `BASE_URL` El protocolo se establece en http.
|
|
||||||
|
|
||||||
### `OPENAI_ORG_ID` (Opcional)
|
|
||||||
|
|
||||||
Especifica el identificador de la organización en OpenAI.
|
|
||||||
|
|
||||||
### `HIDE_USER_API_KEY` (Opcional)
|
|
||||||
|
|
||||||
Si no desea que los usuarios rellenen la clave de API ellos mismos, establezca esta variable de entorno en 1.
|
|
||||||
|
|
||||||
### `DISABLE_GPT4` (Opcional)
|
|
||||||
|
|
||||||
Si no desea que los usuarios utilicen GPT-4, establezca esta variable de entorno en 1.
|
|
||||||
|
|
||||||
### `HIDE_BALANCE_QUERY` (Opcional)
|
|
||||||
|
|
||||||
Si no desea que los usuarios consulte el saldo, establezca esta variable de entorno en 1.
|
|
||||||
|
|
||||||
## explotación
|
|
||||||
|
|
||||||
> No se recomienda encarecidamente desarrollar o implementar localmente, debido a algunas razones técnicas, es difícil configurar el agente API de OpenAI localmente, a menos que pueda asegurarse de que puede conectarse directamente al servidor OpenAI.
|
|
||||||
|
|
||||||
Haga clic en el botón de abajo para iniciar el desarrollo secundario:
|
|
||||||
|
|
||||||
[](https://gitpod.io/#https://github.com/Yidadaa/ChatGPT-Next-Web)
|
|
||||||
|
|
||||||
Antes de empezar a escribir código, debe crear uno nuevo en la raíz del proyecto `.env.local` archivo, lleno de variables de entorno:
|
|
||||||
|
|
||||||
OPENAI_API_KEY=<your api key here>
|
|
||||||
|
|
||||||
### Desarrollo local
|
|
||||||
|
|
||||||
1. Instale nodejs 18 e hilo, pregunte a ChatGPT para obtener más detalles;
|
|
||||||
2. ejecutar `yarn install && yarn dev` Enlatar. ⚠️ Nota: Este comando es solo para desarrollo local, no para implementación.
|
|
||||||
3. Úselo si desea implementar localmente `yarn install && yarn start` comando, puede cooperar con pm2 a daemon para evitar ser asesinado, pregunte a ChatGPT para obtener más detalles.
|
|
||||||
|
|
||||||
## desplegar
|
|
||||||
|
|
||||||
### Implementación de contenedores (recomendado)
|
|
||||||
|
|
||||||
> La versión de Docker debe ser 20 o posterior, de lo contrario se indicará que no se puede encontrar la imagen.
|
|
||||||
|
|
||||||
> ⚠️ Nota: Las versiones de Docker están de 1 a 2 días por detrás de la última versión la mayor parte del tiempo, por lo que es normal que sigas diciendo "La actualización existe" después de la implementación.
|
|
||||||
|
|
||||||
```shell
|
|
||||||
docker pull yidadaa/chatgpt-next-web
|
|
||||||
|
|
||||||
docker run -d -p 3000:3000 \
|
|
||||||
-e OPENAI_API_KEY=sk-xxxx \
|
|
||||||
-e CODE=your-password \
|
|
||||||
yidadaa/chatgpt-next-web
|
|
||||||
```
|
|
||||||
|
|
||||||
También puede especificar proxy:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
docker run -d -p 3000:3000 \
|
|
||||||
-e OPENAI_API_KEY=sk-xxxx \
|
|
||||||
-e CODE=your-password \
|
|
||||||
--net=host \
|
|
||||||
-e PROXY_URL=http://127.0.0.1:7890 \
|
|
||||||
yidadaa/chatgpt-next-web
|
|
||||||
```
|
|
||||||
|
|
||||||
Si necesita especificar otras variables de entorno, agréguelas usted mismo en el comando anterior `-e 环境变量=环境变量值` para especificar.
|
|
||||||
|
|
||||||
### Implementación local
|
|
||||||
|
|
||||||
Ejecute el siguiente comando en la consola:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
bash <(curl -s https://raw.githubusercontent.com/Yidadaa/ChatGPT-Next-Web/main/scripts/setup.sh)
|
|
||||||
```
|
|
||||||
|
|
||||||
⚠️ Nota: Si tiene problemas durante la instalación, utilice la implementación de Docker.
|
|
||||||
|
|
||||||
## Reconocimiento
|
|
||||||
|
|
||||||
### donante
|
|
||||||
|
|
||||||
> Ver versión en inglés.
|
|
||||||
|
|
||||||
### Colaboradores
|
|
||||||
|
|
||||||
[Ver la lista de colaboradores del proyecto](https://github.com/Yidadaa/ChatGPT-Next-Web/graphs/contributors)
|
|
||||||
|
|
||||||
## Licencia de código abierto
|
|
||||||
|
|
||||||
[MIT](https://opensource.org/license/mit/)
|
|
275
README_JA.md
@@ -1,275 +0,0 @@
|
|||||||
<div align="center">
|
|
||||||
<img src="./docs/images/icon.svg" alt="icon"/>
|
|
||||||
|
|
||||||
<h1 align="center">ChatGPT Next Web</h1>
|
|
||||||
|
|
||||||
[English](./README.md) / [简体中文](./README_CN.md) / 日本語
|
|
||||||
|
|
||||||
ワンクリックで、クロスプラットフォーム ChatGPT ウェブ UI が表示されます。
|
|
||||||
|
|
||||||
[![Web][Web-image]][web-url]
|
|
||||||
[![Windows][Windows-image]][download-url]
|
|
||||||
[![MacOS][MacOS-image]][download-url]
|
|
||||||
[![Linux][Linux-image]][download-url]
|
|
||||||
|
|
||||||
[Web App](https://chatgpt.nextweb.fun/) / [Desktop App](https://github.com/Yidadaa/ChatGPT-Next-Web/releases) / [Issues](https://github.com/Yidadaa/ChatGPT-Next-Web/issues) / [Discord](https://discord.gg/YCkeafCafC) / [コーヒーをおごる](https://www.buymeacoffee.com/yidadaa) / [QQ グループ](https://github.com/Yidadaa/ChatGPT-Next-Web/discussions/1724) / [開発者への報酬](https://user-images.githubusercontent.com/16968934/227772541-5bcd52d8-61b7-488c-a203-0330d8006e2b.jpg)
|
|
||||||
|
|
||||||
[web-url]: https://chatgpt.nextweb.fun
|
|
||||||
[download-url]: https://github.com/Yidadaa/ChatGPT-Next-Web/releases
|
|
||||||
[Web-image]: https://img.shields.io/badge/Web-PWA-orange?logo=microsoftedge
|
|
||||||
[Windows-image]: https://img.shields.io/badge/-Windows-blue?logo=windows
|
|
||||||
[MacOS-image]: https://img.shields.io/badge/-MacOS-black?logo=apple
|
|
||||||
[Linux-image]: https://img.shields.io/badge/-Linux-333?logo=ubuntu
|
|
||||||
|
|
||||||
[](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)
|
|
||||||
|
|
||||||
[](https://gitpod.io/#https://github.com/Yidadaa/ChatGPT-Next-Web)
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
## 特徴
|
|
||||||
|
|
||||||
- Vercel で 1 分以内に**ワンクリックで無料デプロイ**。
|
|
||||||
- コンパクトなクライアント (~5MB) on Linux/Windows/MacOS、[今すぐダウンロード](https://github.com/Yidadaa/ChatGPT-Next-Web/releases)
|
|
||||||
- [RWKV-Runner](https://github.com/josStorer/RWKV-Runner) または [LocalAI](https://github.com/go-skynet/LocalAI) との使用をお勧めします
|
|
||||||
- プライバシー第一、すべてのデータはブラウザにローカルに保存されます
|
|
||||||
- マークダウンのサポート: LaTex、マーメイド、コードハイライトなど
|
|
||||||
- レスポンシブデザイン、ダークモード、PWA
|
|
||||||
- 最初の画面読み込み速度が速い(~100kb)、ストリーミングレスポンスをサポート
|
|
||||||
- v2 の新機能:プロンプトテンプレート(マスク)でチャットツールを作成、共有、デバッグ
|
|
||||||
- [awesome-chatgpt-prompts-zh](https://github.com/PlexPt/awesome-chatgpt-prompts-zh) と [awesome-chatgpt-prompts](https://github.com/f/awesome-chatgpt-prompts) による素晴らしいプロンプト
|
|
||||||
- トークンを保存しながら、長い会話をサポートするために自動的にチャット履歴を圧縮します
|
|
||||||
- 国際化: English、简体中文、繁体中文、日本語、Français、Español、Italiano、Türkçe、Deutsch、Tiếng Việt、Русский、Čeština、한국어
|
|
||||||
|
|
||||||
## ロードマップ
|
|
||||||
|
|
||||||
- [x] システムプロンプト: ユーザー定義のプロンプトをシステムプロンプトとして固定 [#138](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/138)
|
|
||||||
- [x] ユーザープロンプト: ユーザはカスタムプロンプトを編集し、プロンプトリストに保存することができます。
|
|
||||||
- [x] プロンプトテンプレート: 事前に定義されたインコンテキストプロンプトで新しいチャットを作成 [#993](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/993)
|
|
||||||
- [x] イメージとして共有、ShareGPT への共有 [#1741](https://github.com/Yidadaa/ChatGPT-Next-Web/pull/1741)
|
|
||||||
- [x] tauri を使ったデスクトップアプリ
|
|
||||||
- [x] セルフホストモデル: [RWKV-Runner](https://github.com/josStorer/RWKV-Runner) と完全に互換性があり、[LocalAI](https://github.com/go-skynet/LocalAI) のサーバーデプロイも可能です: llama/gpt4all/rwkv/vicuna/koala/gpt4all-j/cerebras/falcon/dolly など
|
|
||||||
- [ ] プラグイン: ネットワーク検索、計算機、その他のAPIなどをサポート [#165](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/165)
|
|
||||||
|
|
||||||
## 新機能
|
|
||||||
|
|
||||||
- 🚀 v2.0 がリリースされ、プロンプト・テンプレートが作成できるようになりました!こちらをお読みください: [ChatGPT プロンプトエンジニアリング Tips: ゼロ、一発、数発プロンプト](https://www.allabtai.com/prompt-engineering-tips-zero-one-and-few-shot-prompting/)。
|
|
||||||
- 💡 このプロジェクトをいつでもどこでも簡単に使いたいですか?このデスクトッププラグインをお試しください: https://github.com/mushan0x0/AI0x0.com
|
|
||||||
- 🚀 v2.7 では、会話を画像として共有したり、ShareGPT に共有することができます!
|
|
||||||
- 🚀 v2.8 全てのプラットフォームで動作するクライアントができました!
|
|
||||||
|
|
||||||
## 始める
|
|
||||||
|
|
||||||
> [簡体字中国語 > 始め方](./README_CN.md#开始使用)
|
|
||||||
|
|
||||||
1. [OpenAI API Key](https://platform.openai.com/account/api-keys) を取得する;
|
|
||||||
2. クリック
|
|
||||||
[](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)をクリックします。`CODE` はあなたのページのパスワードであることを忘れないでください;
|
|
||||||
3. お楽しみください :)
|
|
||||||
|
|
||||||
## FAQ
|
|
||||||
|
|
||||||
[簡体字中国語 > よくある質問](./docs/faq-cn.md)
|
|
||||||
|
|
||||||
[English > FAQ](./docs/faq-en.md)
|
|
||||||
|
|
||||||
## 更新を継続する
|
|
||||||
|
|
||||||
> [簡体字中国語 > コードを最新の状態に保つ方法](./README_CN.md#保持更新)
|
|
||||||
|
|
||||||
上記の手順に沿ってワンクリックで自分のプロジェクトをデプロイした場合、"Updates Available" が常に表示される問題に遭遇するかもしれません。これは、Vercel がこのプロジェクトをフォークする代わりに、デフォルトで新しいプロジェクトを作成するため、アップデートを正しく検出できないためです。
|
|
||||||
|
|
||||||
以下の手順で再デプロイすることをお勧めします:
|
|
||||||
|
|
||||||
- 元のリポジトリを削除してください;
|
|
||||||
- ページの右上にあるフォークボタンを使って、このプロジェクトをフォークする;
|
|
||||||
- Vercel を選択し、再度デプロイする。[詳しいチュートリアルを参照](./docs/vercel-cn.md)。
|
|
||||||
|
|
||||||
### 自動アップデートを有効にする
|
|
||||||
|
|
||||||
> Upstream Sync の実行に失敗した場合は、手動で一度フォークしてください。
|
|
||||||
|
|
||||||
プロジェクトをフォークした後、GitHub の制限により、フォークしたプロジェクトの Actions ページで Workflows と Upstream Sync Action を手動で有効にする必要があります。有効にすると、1 時間ごとに自動更新がスケジュールされます:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
### 手動でコードを更新する
|
|
||||||
|
|
||||||
すぐに更新したい場合は、[GitHub ドキュメント](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork) をチェックして、フォークしたプロジェクトを上流のコードと同期させる方法を学んでください。
|
|
||||||
|
|
||||||
このプロジェクトにスターをつけたり、ウォッチしたり、作者をフォローすることで、リリースの通知を受け取ることができます。
|
|
||||||
|
|
||||||
## アクセスパスワード
|
|
||||||
|
|
||||||
> [簡体字中国語 > アクセスパスワードを増やす方法](./README_CN.md#配置页面访问密码)
|
|
||||||
|
|
||||||
このプロジェクトではアクセス制御を制限しています。vercel の環境変数のページに `CODE` という環境変数を追加してください。その値は次のようにカンマで区切られたパスワードでなければなりません:
|
|
||||||
|
|
||||||
```
|
|
||||||
code1,code2,code3
|
|
||||||
```
|
|
||||||
|
|
||||||
この環境変数を追加または変更した後は、変更を有効にするためにプロジェクトを再デプロイしてください。
|
|
||||||
|
|
||||||
## 環境変数
|
|
||||||
|
|
||||||
> [簡体字中国語 > API キー、アクセスパスワード、インターフェイスプロキシ設定方法](./README_CN.md#环境变量)
|
|
||||||
|
|
||||||
### `OPENAI_API_KEY` (必須)
|
|
||||||
|
|
||||||
OpenAI の api キー。
|
|
||||||
|
|
||||||
### `CODE` (オプション)
|
|
||||||
|
|
||||||
カンマで区切られたアクセスパスワード。
|
|
||||||
|
|
||||||
### `BASE_URL` (オプション)
|
|
||||||
|
|
||||||
> デフォルト: `https://api.openai.com`
|
|
||||||
|
|
||||||
> 例: `http://your-openai-proxy.com`
|
|
||||||
|
|
||||||
OpenAI api のリクエストベースの url をオーバーライドします。
|
|
||||||
|
|
||||||
### `OPENAI_ORG_ID` (オプション)
|
|
||||||
|
|
||||||
OpenAI の組織 ID を指定します。
|
|
||||||
|
|
||||||
### `HIDE_USER_API_KEY` (オプション)
|
|
||||||
|
|
||||||
> デフォルト: 空
|
|
||||||
|
|
||||||
ユーザーに自分の API キーを入力させたくない場合は、この値を 1 に設定する。
|
|
||||||
|
|
||||||
### `DISABLE_GPT4` (オプション)
|
|
||||||
|
|
||||||
> デフォルト: 空
|
|
||||||
|
|
||||||
ユーザーに GPT-4 を使用させたくない場合は、この値を 1 に設定する。
|
|
||||||
|
|
||||||
### `HIDE_BALANCE_QUERY` (オプション)
|
|
||||||
|
|
||||||
> デフォルト: 空
|
|
||||||
|
|
||||||
ユーザーに残高を照会させたくない場合は、この値を 1 に設定する。
|
|
||||||
|
|
||||||
## 必要条件
|
|
||||||
|
|
||||||
NodeJS >= 18、Docker >= 20
|
|
||||||
|
|
||||||
## Development
|
|
||||||
|
|
||||||
> [簡体字中国語 > 二次開発の進め方](./README_CN.md#开发)
|
|
||||||
|
|
||||||
[](https://gitpod.io/#https://github.com/Yidadaa/ChatGPT-Next-Web)
|
|
||||||
|
|
||||||
開発を始める前に、プロジェクトのルートに新しい `.env.local` ファイルを作成し、そこに api キーを置く必要があります:
|
|
||||||
|
|
||||||
```
|
|
||||||
OPENAI_API_KEY=<your api key here>
|
|
||||||
|
|
||||||
# OpenAI サービスにアクセスできない場合は、この BASE_URL を使用してください
|
|
||||||
BASE_URL=https://chatgpt1.nextweb.fun/api/proxy
|
|
||||||
```
|
|
||||||
|
|
||||||
### ローカルデプロイ
|
|
||||||
|
|
||||||
```shell
|
|
||||||
# 1. nodejs と yarn をまずインストールする
|
|
||||||
# 2. `.env.local` にローカルの env vars を設定する
|
|
||||||
# 3. 実行
|
|
||||||
yarn install
|
|
||||||
yarn dev
|
|
||||||
```
|
|
||||||
|
|
||||||
## デプロイ
|
|
||||||
|
|
||||||
> [簡体字中国語 > プライベートサーバーへのデプロイ方法](./README_CN.md#部署)
|
|
||||||
|
|
||||||
### Docker (推奨)
|
|
||||||
|
|
||||||
```shell
|
|
||||||
docker pull yidadaa/chatgpt-next-web
|
|
||||||
|
|
||||||
docker run -d -p 3000:3000 \
|
|
||||||
-e OPENAI_API_KEY=sk-xxxx \
|
|
||||||
-e CODE=your-password \
|
|
||||||
yidadaa/chatgpt-next-web
|
|
||||||
```
|
|
||||||
|
|
||||||
プロキシの後ろでサービスを開始することができる:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
docker run -d -p 3000:3000 \
|
|
||||||
-e OPENAI_API_KEY=sk-xxxx \
|
|
||||||
-e CODE=your-password \
|
|
||||||
-e PROXY_URL=http://localhost:7890 \
|
|
||||||
yidadaa/chatgpt-next-web
|
|
||||||
```
|
|
||||||
|
|
||||||
プロキシにパスワードが必要な場合:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
-e PROXY_URL="http://127.0.0.1:7890 user pass"
|
|
||||||
```
|
|
||||||
|
|
||||||
### シェル
|
|
||||||
|
|
||||||
```shell
|
|
||||||
bash <(curl -s https://raw.githubusercontent.com/Yidadaa/ChatGPT-Next-Web/main/scripts/setup.sh)
|
|
||||||
```
|
|
||||||
|
|
||||||
## スクリーンショット
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## 翻訳
|
|
||||||
|
|
||||||
新しい翻訳を追加したい場合は、この[ドキュメント](./docs/translation.md)をお読みください。
|
|
||||||
|
|
||||||
## 寄付
|
|
||||||
|
|
||||||
[コーヒーをおごる](https://www.buymeacoffee.com/yidadaa)
|
|
||||||
|
|
||||||
## スペシャルサンクス
|
|
||||||
|
|
||||||
### スポンサー
|
|
||||||
|
|
||||||
> 寄付金額が 100 元以上のユーザーのみリストアップしています
|
|
||||||
|
|
||||||
[@mushan0x0](https://github.com/mushan0x0)
|
|
||||||
[@ClarenceDan](https://github.com/ClarenceDan)
|
|
||||||
[@zhangjia](https://github.com/zhangjia)
|
|
||||||
[@hoochanlon](https://github.com/hoochanlon)
|
|
||||||
[@relativequantum](https://github.com/relativequantum)
|
|
||||||
[@desenmeng](https://github.com/desenmeng)
|
|
||||||
[@webees](https://github.com/webees)
|
|
||||||
[@chazzhou](https://github.com/chazzhou)
|
|
||||||
[@hauy](https://github.com/hauy)
|
|
||||||
[@Corwin006](https://github.com/Corwin006)
|
|
||||||
[@yankunsong](https://github.com/yankunsong)
|
|
||||||
[@ypwhs](https://github.com/ypwhs)
|
|
||||||
[@fxxxchao](https://github.com/fxxxchao)
|
|
||||||
[@hotic](https://github.com/hotic)
|
|
||||||
[@WingCH](https://github.com/WingCH)
|
|
||||||
[@jtung4](https://github.com/jtung4)
|
|
||||||
[@micozhu](https://github.com/micozhu)
|
|
||||||
[@jhansion](https://github.com/jhansion)
|
|
||||||
[@Sha1rholder](https://github.com/Sha1rholder)
|
|
||||||
[@AnsonHyq](https://github.com/AnsonHyq)
|
|
||||||
[@synwith](https://github.com/synwith)
|
|
||||||
[@piksonGit](https://github.com/piksonGit)
|
|
||||||
|
|
||||||
### コントリビューター
|
|
||||||
|
|
||||||
[コントリビューター達](https://github.com/Yidadaa/ChatGPT-Next-Web/graphs/contributors)
|
|
||||||
|
|
||||||
## ライセンス
|
|
||||||
|
|
||||||
[MIT](https://opensource.org/license/mit/)
|
|
187
README_KO.md
@@ -1,187 +0,0 @@
|
|||||||
<div align="center">
|
|
||||||
<img src="./docs/images/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)
|
|
||||||
|
|
||||||
[](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)
|
|
||||||
|
|
||||||
[](https://gitpod.io/#https://github.com/Yidadaa/ChatGPT-Next-Web)
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
## 사용 시작
|
|
||||||
|
|
||||||
1. [OpenAI API Key](https://platform.openai.com/account/api-keys)를 준비합니다.
|
|
||||||
2. 오른쪽 버튼을 클릭하여 배포를 시작하십시오:
|
|
||||||
[](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와 [페이지 접근 비밀번호](#페이지-접근-비밀번호-설정) CODE를 환경 변수 페이지에 입력하십시오.
|
|
||||||
3. 배포가 완료되면 사용을 시작하십시오.
|
|
||||||
4. (선택 사항) [사용자 정의 도메인 바인딩](https://vercel.com/docs/concepts/projects/domains/add-a-domain) : Vercel에서 할당한 도메인 DNS가 일부 지역에서 오염되어 있습니다. 사용자 정의 도메인을 바인딩하면 직접 연결할 수 있습니다.
|
|
||||||
|
|
||||||
## 업데이트 유지
|
|
||||||
|
|
||||||
위의 단계대로 프로젝트를 배포한 경우 "업데이트가 있습니다"라는 메시지가 항상 표시될 수 있습니다. 이는 Vercel이 기본적으로 새 프로젝트를 생성하고이 프로젝트를 포크하지 않기 때문입니다. 이 문제는 업데이트를 올바르게 감지할 수 없습니다.
|
|
||||||
아래 단계를 따라 다시 배포하십시오:
|
|
||||||
|
|
||||||
- 기존 저장소를 삭제합니다.
|
|
||||||
- 페이지 오른쪽 상단의 포크 버튼을 사용하여 이 프로젝트를 포크합니다.
|
|
||||||
- Vercel에서 다시 선택하여 배포하십시오. [자세한 튜토리얼 보기](./docs/vercel-cn.md#새-프로젝트-만드는-방법).
|
|
||||||
|
|
||||||
### 자동 업데이트 활성화
|
|
||||||
|
|
||||||
> Upstream Sync 오류가 발생한 경우 수동으로 Sync Fork를 한 번 실행하십시오!
|
|
||||||
|
|
||||||
프로젝트를 포크한 후 GitHub의 제한으로 인해 포크한 프로젝트의 동작 페이지에서 워크플로우를 수동으로 활성화해야 합니다. Upstream Sync Action을 활성화하면 매시간마다 자동 업데이트가 활성화됩니다:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
### 수동으로 코드 업데이트
|
|
||||||
|
|
||||||
수동으로 즉시 업데이트하려면 [GitHub 문서](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork)에서 포크된 프로젝트를 어떻게 원본 코드와 동기화하는지 확인하십시오.
|
|
||||||
|
|
||||||
이 프로젝트에 별표/감시를 부여하거나 작성자를 팔로우하여 새 기능 업데이트 알림을 받을 수 있습니다.
|
|
||||||
|
|
||||||
## 페이지 접근 비밀번호 설정
|
|
||||||
|
|
||||||
> 비밀번호가 설정된 후, 사용자는 설정 페이지에서 접근 코드를 수동으로 입력하여 정상적으로 채팅할 수 있습니다. 그렇지 않으면 메시지를 통해 권한이 없는 상태가 표시됩니다.
|
|
||||||
|
|
||||||
> **경고** : 비밀번호의 길이를 충분히 길게 설정하십시오. 최소 7 자리 이상이 좋습니다. 그렇지 않으면 [해킹될 수 있습니다](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/518).
|
|
||||||
|
|
||||||
이 프로젝트는 제한된 권한 제어 기능을 제공합니다. Vercel 프로젝트 컨트롤 패널의 환경 변수 페이지에서 `CODE`라는 환경 변수를 추가하십시오. 값은 쉼표로 구분된 사용자 정의 비밀번호로 설정됩니다. (아래 예시의 경우 `code1` `code2` `code3` 3개의 비밀번호가 생성됩니다.)
|
|
||||||
|
|
||||||
```
|
|
||||||
code1,code2,code3
|
|
||||||
```
|
|
||||||
|
|
||||||
이 환경 변수를 추가하거나 수정한 후에는 프로젝트를 다시 배포하여 변경 사항을 적용해야 합니다.
|
|
||||||
|
|
||||||
## 환경 변수
|
|
||||||
> 이 프로젝트에서 대부분의 설정 요소들은 환경 변수를 통해 설정됩니다. [Vercel 환경변수 수정 방법.](./docs/vercel-ko.md)。
|
|
||||||
|
|
||||||
## OPENAI_API_KEY (필수 항목)
|
|
||||||
|
|
||||||
OpenAI 키로, openai 계정 페이지에서 신청한 api key입니다.
|
|
||||||
|
|
||||||
## CODE (선택 가능)
|
|
||||||
|
|
||||||
접근 비밀번호로, 선택적입니다. 쉼표를 사용하여 여러 비밀번호를 구분할 수 있습니다.
|
|
||||||
|
|
||||||
**경고** : 이 항목을 입력하지 않으면, 누구나 여러분이 배포한 웹사이트를 직접 사용할 수 있게 됩니다. 이로 인해 토큰이 빠르게 소진될 수 있으므로, 이 항목을 반드시 입력하는 것이 좋습니다.
|
|
||||||
|
|
||||||
## BASE_URL (선택 가능)
|
|
||||||
|
|
||||||
> 기본값: `https://api.openai.com`
|
|
||||||
|
|
||||||
> 예시: `http://your-openai-proxy.com`
|
|
||||||
|
|
||||||
OpenAI 인터페이스 프록시 URL입니다. 만약, 수동으로 openai 인터페이스 proxy를 설정했다면, 이 항목을 입력하셔야 합니다.
|
|
||||||
|
|
||||||
**참고**: SSL 인증서 문제가 발생한 경우, BASE_URL의 프로토콜을 http로 설정하세요.
|
|
||||||
|
|
||||||
## OPENAI_ORG_ID (선택 가능)
|
|
||||||
|
|
||||||
OpenAI 내의 조직 ID를 지정합니다.
|
|
||||||
|
|
||||||
## HIDE_USER_API_KEY (선택 가능)
|
|
||||||
|
|
||||||
사용자가 API Key를 직접 입력하는 것을 원하지 않는 경우, 이 환경 변수를 1로 설정하세요.
|
|
||||||
|
|
||||||
## DISABLE_GPT4 (선택 가능)
|
|
||||||
|
|
||||||
사용자가 GPT-4를 사용하는 것을 원하지 않는 경우, 이 환경 변수를 1로 설정하세요.
|
|
||||||
|
|
||||||
## HIDE_BALANCE_QUERY (선택 가능)
|
|
||||||
|
|
||||||
사용자가 잔액을 조회하는 것을 원하지 않는 경우, 이 환경 변수를 1로 설정하세요.
|
|
||||||
|
|
||||||
## 개발
|
|
||||||
|
|
||||||
아래 버튼을 클릭하여 개발을 시작하세요:
|
|
||||||
|
|
||||||
[](https://gitpod.io/#https://github.com/Yidadaa/ChatGPT-Next-Web)
|
|
||||||
|
|
||||||
코드 작성을 전, 프로젝트 루트 디렉토리에 `.env.local` 파일을 새로 만들고 해당 파일에 환경 변수를 입력해야 합니다:
|
|
||||||
|
|
||||||
```
|
|
||||||
OPENAI_API_KEY=<여기에 여러분의 api 키를 입력하세요>
|
|
||||||
|
|
||||||
#중국 사용자들은 이 프로젝트에 포함된 프록시를 사용하여 개발할 수 있습니다. 또는 다른 프록시 주소를 자유롭게 선택할 수 있습니다.
|
|
||||||
BASE_URL=https://chatgpt1.nextweb.fun/api/proxy
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
### 로컬 환경에서의 개발
|
|
||||||
|
|
||||||
1. nodejs 18과 yarn을 설치하세요. 자세한 사항은 ChatGPT에 문의하십시오.
|
|
||||||
2. `yarn install && yarn dev` 명령을 실행하세요. ⚠️ 주의: 이 명령은 로컬 개발 전용입니다. 배포용으로 사용하지 마십시오!
|
|
||||||
3. 로컬에서 배포하고 싶다면, `yarn install && yarn build && yarn start` 명령을 사용하세요. pm2와 함께 사용하여 프로세스를 보호하고, 강제 종료되지 않도록 할 수 있습니다. 자세한 내용은 ChatGPT에 문의하세요.
|
|
||||||
|
|
||||||
## 배포
|
|
||||||
|
|
||||||
### 컨테이너 배포 (추천)
|
|
||||||
|
|
||||||
> Docker 버전은 20 이상이어야 합니다. 그렇지 않으면 이미지를 찾을 수 없다는 메시지가 표시됩니다.
|
|
||||||
|
|
||||||
> ⚠️ 주의: docker 버전은 대부분의 경우 최신 버전보다 1~2일 뒤처집니다. 따라서 배포 후 "업데이트 가능" 알림이 지속적으로 나타날 수 있으며, 이는 정상적인 현상입니다.
|
|
||||||
|
|
||||||
```shell
|
|
||||||
docker pull yidadaa/chatgpt-next-web
|
|
||||||
|
|
||||||
docker run -d -p 3000:3000 \
|
|
||||||
-e OPENAI_API_KEY=sk-xxxx \
|
|
||||||
-e CODE=페이지 접근 비밀번호 \
|
|
||||||
yidadaa/chatgpt-next-web
|
|
||||||
```
|
|
||||||
|
|
||||||
프록시를 지정하려면 다음을 사용하세요:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
docker run -d -p 3000:3000 \
|
|
||||||
-e OPENAI_API_KEY=sk-xxxx \
|
|
||||||
-e CODE=페이지 접근 비밀번호 \
|
|
||||||
--net=host \
|
|
||||||
-e PROXY_URL=http://127.0.0.1:7890 \
|
|
||||||
yidadaa/chatgpt-next-web
|
|
||||||
```
|
|
||||||
|
|
||||||
로컬 프록시에 사용자 이름과 비밀번호가 필요한 경우, 아래와 같이 사용하세요:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
-e PROXY_URL="http://127.0.0.1:7890 사용자이름 비밀번호"
|
|
||||||
```
|
|
||||||
|
|
||||||
다른 환경 변수를 지정해야 하는 경우, 위의 명령에 `-e 환경변수=환경변수값`을 추가하여 지정하세요.
|
|
||||||
|
|
||||||
### 로컬 배포
|
|
||||||
|
|
||||||
콘솔에서 아래의 명령을 실행하세요:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
bash <(curl -s https://raw.githubusercontent.com/Yidadaa/ChatGPT-Next-Web/main/scripts/setup.sh)
|
|
||||||
```
|
|
||||||
|
|
||||||
⚠️ 주의: 설치 중 문제가 발생한 경우, docker로 배포하세요.
|
|
||||||
|
|
||||||
## 감사의 말
|
|
||||||
|
|
||||||
### 기부자
|
|
||||||
|
|
||||||
> 영문 버전 참조.
|
|
||||||
|
|
||||||
### 기여자
|
|
||||||
|
|
||||||
[프로젝트 기여자 목록 보기](https://github.com/Yidadaa/ChatGPT-Next-Web/graphs/contributors)
|
|
||||||
|
|
||||||
### 관련 프로젝트
|
|
||||||
- [one-api](https://github.com/songquanpeng/one-api): 통합 대형 모델 할당 관리 플랫폼, 주요 대형 언어 모델 모두 지원
|
|
||||||
|
|
||||||
## 오픈소스 라이센스
|
|
||||||
|
|
||||||
[MIT](https://opensource.org/license/mit/)
|
|
@@ -28,7 +28,7 @@ export function auth(req: NextRequest) {
|
|||||||
const authToken = req.headers.get("Authorization") ?? "";
|
const authToken = req.headers.get("Authorization") ?? "";
|
||||||
|
|
||||||
// check if it is openai api key or user token
|
// check if it is openai api key or user token
|
||||||
const { accessCode, apiKey: token } = parseApiKey(authToken);
|
const { accessCode, apiKey } = parseApiKey(authToken);
|
||||||
|
|
||||||
const hashedCode = md5.hash(accessCode ?? "").trim();
|
const hashedCode = md5.hash(accessCode ?? "").trim();
|
||||||
|
|
||||||
@@ -39,19 +39,32 @@ export function auth(req: NextRequest) {
|
|||||||
console.log("[User IP] ", getIP(req));
|
console.log("[User IP] ", getIP(req));
|
||||||
console.log("[Time] ", new Date().toLocaleString());
|
console.log("[Time] ", new Date().toLocaleString());
|
||||||
|
|
||||||
if (serverConfig.needCode && !serverConfig.codes.has(hashedCode) && !token) {
|
if (serverConfig.needCode && !serverConfig.codes.has(hashedCode) && !apiKey) {
|
||||||
return {
|
return {
|
||||||
error: true,
|
error: true,
|
||||||
msg: !accessCode ? "empty access code" : "wrong access code",
|
msg: !accessCode ? "empty access code" : "wrong access code",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (serverConfig.hideUserApiKey && !!apiKey) {
|
||||||
|
return {
|
||||||
|
error: true,
|
||||||
|
msg: "you are not allowed to access openai with your own api key",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// if user does not provide an api key, inject system api key
|
// if user does not provide an api key, inject system api key
|
||||||
if (!token) {
|
if (!apiKey) {
|
||||||
const apiKey = serverConfig.apiKey;
|
const serverApiKey = serverConfig.isAzure
|
||||||
if (apiKey) {
|
? serverConfig.azureApiKey
|
||||||
|
: serverConfig.apiKey;
|
||||||
|
|
||||||
|
if (serverApiKey) {
|
||||||
console.log("[Auth] use system api key");
|
console.log("[Auth] use system api key");
|
||||||
req.headers.set("Authorization", `Bearer ${apiKey}`);
|
req.headers.set(
|
||||||
|
"Authorization",
|
||||||
|
`${serverConfig.isAzure ? "" : "Bearer "}${serverApiKey}`,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
console.log("[Auth] admin did not provide an api key");
|
console.log("[Auth] admin did not provide an api key");
|
||||||
}
|
}
|
||||||
|
@@ -1,48 +1,65 @@
|
|||||||
import { NextRequest, NextResponse } from "next/server";
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
import { getServerSideConfig } from "../config/server";
|
||||||
|
import { DEFAULT_MODELS, OPENAI_BASE_URL } from "../constant";
|
||||||
|
import { collectModelTable } from "../utils/model";
|
||||||
|
import { makeAzurePath } from "../azure";
|
||||||
|
|
||||||
export const OPENAI_URL = "api.openai.com";
|
const serverConfig = getServerSideConfig();
|
||||||
const DEFAULT_PROTOCOL = "https";
|
|
||||||
const PROTOCOL = process.env.PROTOCOL || DEFAULT_PROTOCOL;
|
|
||||||
const BASE_URL = process.env.BASE_URL || OPENAI_URL;
|
|
||||||
const DISABLE_GPT4 = !!process.env.DISABLE_GPT4;
|
|
||||||
|
|
||||||
export async function requestOpenai(req: NextRequest) {
|
export async function requestOpenai(req: NextRequest) {
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
|
|
||||||
const authValue = req.headers.get("Authorization") ?? "";
|
const authValue = req.headers.get("Authorization") ?? "";
|
||||||
const openaiPath = `${req.nextUrl.pathname}${req.nextUrl.search}`.replaceAll(
|
const authHeaderName = serverConfig.isAzure ? "api-key" : "Authorization";
|
||||||
|
|
||||||
|
let path = `${req.nextUrl.pathname}${req.nextUrl.search}`.replaceAll(
|
||||||
"/api/openai/",
|
"/api/openai/",
|
||||||
"",
|
"",
|
||||||
);
|
);
|
||||||
|
|
||||||
let baseUrl = BASE_URL;
|
let baseUrl =
|
||||||
|
serverConfig.azureUrl || serverConfig.baseUrl || OPENAI_BASE_URL;
|
||||||
|
|
||||||
if (!baseUrl.startsWith("http")) {
|
if (!baseUrl.startsWith("http")) {
|
||||||
baseUrl = `${PROTOCOL}://${baseUrl}`;
|
baseUrl = `https://${baseUrl}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (baseUrl.endsWith('/')) {
|
if (baseUrl.endsWith("/")) {
|
||||||
baseUrl = baseUrl.slice(0, -1);
|
baseUrl = baseUrl.slice(0, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("[Proxy] ", openaiPath);
|
console.log("[Proxy] ", path);
|
||||||
console.log("[Base Url]", baseUrl);
|
console.log("[Base Url]", baseUrl);
|
||||||
|
// this fix [Org ID] undefined in server side if not using custom point
|
||||||
if (process.env.OPENAI_ORG_ID) {
|
if (serverConfig.openaiOrgId !== undefined) {
|
||||||
console.log("[Org ID]", process.env.OPENAI_ORG_ID);
|
console.log("[Org ID]", serverConfig.openaiOrgId);
|
||||||
}
|
}
|
||||||
|
|
||||||
const timeoutId = setTimeout(() => {
|
const timeoutId = setTimeout(
|
||||||
controller.abort();
|
() => {
|
||||||
}, 10 * 60 * 1000);
|
controller.abort();
|
||||||
|
},
|
||||||
|
10 * 60 * 1000,
|
||||||
|
);
|
||||||
|
|
||||||
const fetchUrl = `${baseUrl}/${openaiPath}`;
|
if (serverConfig.isAzure) {
|
||||||
|
if (!serverConfig.azureApiVersion) {
|
||||||
|
return NextResponse.json({
|
||||||
|
error: true,
|
||||||
|
message: `missing AZURE_API_VERSION in server env vars`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
path = makeAzurePath(path, serverConfig.azureApiVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchUrl = `${baseUrl}/${path}`;
|
||||||
const fetchOptions: RequestInit = {
|
const fetchOptions: RequestInit = {
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
"Cache-Control": "no-store",
|
"Cache-Control": "no-store",
|
||||||
Authorization: authValue,
|
[authHeaderName]: authValue,
|
||||||
...(process.env.OPENAI_ORG_ID && {
|
...(serverConfig.openaiOrgId && {
|
||||||
"OpenAI-Organization": process.env.OPENAI_ORG_ID,
|
"OpenAI-Organization": serverConfig.openaiOrgId,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
method: req.method,
|
method: req.method,
|
||||||
@@ -55,18 +72,23 @@ export async function requestOpenai(req: NextRequest) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// #1815 try to refuse gpt4 request
|
// #1815 try to refuse gpt4 request
|
||||||
if (DISABLE_GPT4 && req.body) {
|
if (serverConfig.customModels && req.body) {
|
||||||
try {
|
try {
|
||||||
|
const modelTable = collectModelTable(
|
||||||
|
DEFAULT_MODELS,
|
||||||
|
serverConfig.customModels,
|
||||||
|
);
|
||||||
const clonedBody = await req.text();
|
const clonedBody = await req.text();
|
||||||
fetchOptions.body = clonedBody;
|
fetchOptions.body = clonedBody;
|
||||||
|
|
||||||
const jsonBody = JSON.parse(clonedBody);
|
const jsonBody = JSON.parse(clonedBody) as { model?: string };
|
||||||
|
|
||||||
if ((jsonBody?.model ?? "").includes("gpt-4")) {
|
// not undefined and is false
|
||||||
|
if (modelTable[jsonBody?.model ?? ""].available === false) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{
|
{
|
||||||
error: true,
|
error: true,
|
||||||
message: "you are not allowed to use gpt-4 model",
|
message: `you are not allowed to use ${jsonBody?.model} model`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
status: 403,
|
status: 403,
|
||||||
|
@@ -11,6 +11,8 @@ const DANGER_CONFIG = {
|
|||||||
hideUserApiKey: serverConfig.hideUserApiKey,
|
hideUserApiKey: serverConfig.hideUserApiKey,
|
||||||
disableGPT4: serverConfig.disableGPT4,
|
disableGPT4: serverConfig.disableGPT4,
|
||||||
hideBalanceQuery: serverConfig.hideBalanceQuery,
|
hideBalanceQuery: serverConfig.hideBalanceQuery,
|
||||||
|
disableFastLink: serverConfig.disableFastLink,
|
||||||
|
customModels: serverConfig.customModels,
|
||||||
};
|
};
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
@@ -75,3 +75,4 @@ export const GET = handle;
|
|||||||
export const POST = handle;
|
export const POST = handle;
|
||||||
|
|
||||||
export const runtime = "edge";
|
export const runtime = "edge";
|
||||||
|
export const preferredRegion = ['arn1', 'bom1', 'cdg1', 'cle1', 'cpt1', 'dub1', 'fra1', 'gru1', 'hnd1', 'iad1', 'icn1', 'kix1', 'lhr1', 'pdx1', 'sfo1', 'sin1', 'syd1'];
|
||||||
|
9
app/azure.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
export function makeAzurePath(path: string, apiVersion: string) {
|
||||||
|
// should omit /v1 prefix
|
||||||
|
path = path.replaceAll("v1/", "");
|
||||||
|
|
||||||
|
// should add api-key to query string
|
||||||
|
path += `${path.includes("?") ? "&" : "?"}api-version=${apiVersion}`;
|
||||||
|
|
||||||
|
return path;
|
||||||
|
}
|
@@ -1,5 +1,5 @@
|
|||||||
import { getClientConfig } from "../config/client";
|
import { getClientConfig } from "../config/client";
|
||||||
import { ACCESS_CODE_PREFIX } from "../constant";
|
import { ACCESS_CODE_PREFIX, Azure, ServiceProvider } from "../constant";
|
||||||
import { ChatMessage, ModelType, useAccessStore } from "../store";
|
import { ChatMessage, ModelType, useAccessStore } from "../store";
|
||||||
import { ChatGPTApi } from "./platforms/openai";
|
import { ChatGPTApi } from "./platforms/openai";
|
||||||
|
|
||||||
@@ -127,22 +127,26 @@ export const api = new ClientApi();
|
|||||||
|
|
||||||
export function getHeaders() {
|
export function getHeaders() {
|
||||||
const accessStore = useAccessStore.getState();
|
const accessStore = useAccessStore.getState();
|
||||||
let headers: Record<string, string> = {
|
const headers: Record<string, string> = {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
"x-requested-with": "XMLHttpRequest",
|
"x-requested-with": "XMLHttpRequest",
|
||||||
};
|
};
|
||||||
|
|
||||||
const makeBearer = (token: string) => `Bearer ${token.trim()}`;
|
const isAzure = accessStore.provider === ServiceProvider.Azure;
|
||||||
|
const authHeader = isAzure ? "api-key" : "Authorization";
|
||||||
|
const apiKey = isAzure ? accessStore.azureApiKey : accessStore.openaiApiKey;
|
||||||
|
|
||||||
|
const makeBearer = (s: string) => `${isAzure ? "" : "Bearer "}${s.trim()}`;
|
||||||
const validString = (x: string) => x && x.length > 0;
|
const validString = (x: string) => x && x.length > 0;
|
||||||
|
|
||||||
// use user's api key first
|
// use user's api key first
|
||||||
if (validString(accessStore.token)) {
|
if (validString(apiKey)) {
|
||||||
headers.Authorization = makeBearer(accessStore.token);
|
headers[authHeader] = makeBearer(apiKey);
|
||||||
} else if (
|
} else if (
|
||||||
accessStore.enabledAccessControl() &&
|
accessStore.enabledAccessControl() &&
|
||||||
validString(accessStore.accessCode)
|
validString(accessStore.accessCode)
|
||||||
) {
|
) {
|
||||||
headers.Authorization = makeBearer(
|
headers[authHeader] = makeBearer(
|
||||||
ACCESS_CODE_PREFIX + accessStore.accessCode,
|
ACCESS_CODE_PREFIX + accessStore.accessCode,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -1,8 +1,10 @@
|
|||||||
import {
|
import {
|
||||||
|
ApiPath,
|
||||||
DEFAULT_API_HOST,
|
DEFAULT_API_HOST,
|
||||||
DEFAULT_MODELS,
|
DEFAULT_MODELS,
|
||||||
OpenaiPath,
|
OpenaiPath,
|
||||||
REQUEST_TIMEOUT_MS,
|
REQUEST_TIMEOUT_MS,
|
||||||
|
ServiceProvider,
|
||||||
} from "@/app/constant";
|
} from "@/app/constant";
|
||||||
import { useAccessStore, useAppConfig, useChatStore } from "@/app/store";
|
import { useAccessStore, useAppConfig, useChatStore } from "@/app/store";
|
||||||
|
|
||||||
@@ -14,6 +16,7 @@ import {
|
|||||||
} from "@fortaine/fetch-event-source";
|
} from "@fortaine/fetch-event-source";
|
||||||
import { prettyObject } from "@/app/utils/format";
|
import { prettyObject } from "@/app/utils/format";
|
||||||
import { getClientConfig } from "@/app/config/client";
|
import { getClientConfig } from "@/app/config/client";
|
||||||
|
import { makeAzurePath } from "@/app/azure";
|
||||||
|
|
||||||
export interface OpenAIListModelResponse {
|
export interface OpenAIListModelResponse {
|
||||||
object: string;
|
object: string;
|
||||||
@@ -28,20 +31,35 @@ export class ChatGPTApi implements LLMApi {
|
|||||||
private disableListModels = true;
|
private disableListModels = true;
|
||||||
|
|
||||||
path(path: string): string {
|
path(path: string): string {
|
||||||
let openaiUrl = useAccessStore.getState().openaiUrl;
|
const accessStore = useAccessStore.getState();
|
||||||
const apiPath = "/api/openai";
|
|
||||||
|
|
||||||
if (openaiUrl.length === 0) {
|
const isAzure = accessStore.provider === ServiceProvider.Azure;
|
||||||
|
|
||||||
|
if (isAzure && !accessStore.isValidAzure()) {
|
||||||
|
throw Error(
|
||||||
|
"incomplete azure config, please check it in your settings page",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let baseUrl = isAzure ? accessStore.azureUrl : accessStore.openaiUrl;
|
||||||
|
|
||||||
|
if (baseUrl.length === 0) {
|
||||||
const isApp = !!getClientConfig()?.isApp;
|
const isApp = !!getClientConfig()?.isApp;
|
||||||
openaiUrl = isApp ? DEFAULT_API_HOST : apiPath;
|
baseUrl = isApp ? DEFAULT_API_HOST : ApiPath.OpenAI;
|
||||||
}
|
}
|
||||||
if (openaiUrl.endsWith("/")) {
|
|
||||||
openaiUrl = openaiUrl.slice(0, openaiUrl.length - 1);
|
if (baseUrl.endsWith("/")) {
|
||||||
|
baseUrl = baseUrl.slice(0, baseUrl.length - 1);
|
||||||
}
|
}
|
||||||
if (!openaiUrl.startsWith("http") && !openaiUrl.startsWith(apiPath)) {
|
if (!baseUrl.startsWith("http") && !baseUrl.startsWith(ApiPath.OpenAI)) {
|
||||||
openaiUrl = "https://" + openaiUrl;
|
baseUrl = "https://" + baseUrl;
|
||||||
}
|
}
|
||||||
return [openaiUrl, path].join("/");
|
|
||||||
|
if (isAzure) {
|
||||||
|
path = makeAzurePath(path, accessStore.azureApiVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [baseUrl, path].join("/");
|
||||||
}
|
}
|
||||||
|
|
||||||
extractMessage(res: any) {
|
extractMessage(res: any) {
|
||||||
@@ -70,6 +88,8 @@ export class ChatGPTApi implements LLMApi {
|
|||||||
presence_penalty: modelConfig.presence_penalty,
|
presence_penalty: modelConfig.presence_penalty,
|
||||||
frequency_penalty: modelConfig.frequency_penalty,
|
frequency_penalty: modelConfig.frequency_penalty,
|
||||||
top_p: modelConfig.top_p,
|
top_p: modelConfig.top_p,
|
||||||
|
// max_tokens: Math.max(modelConfig.max_tokens, 1024),
|
||||||
|
// Please do not ask me why not send max_tokens, no reason, this param is just shit, I dont want to explain anymore.
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log("[Request] openai payload: ", requestPayload);
|
console.log("[Request] openai payload: ", requestPayload);
|
||||||
@@ -95,12 +115,35 @@ export class ChatGPTApi implements LLMApi {
|
|||||||
|
|
||||||
if (shouldStream) {
|
if (shouldStream) {
|
||||||
let responseText = "";
|
let responseText = "";
|
||||||
|
let remainText = "";
|
||||||
let finished = false;
|
let finished = false;
|
||||||
|
|
||||||
|
// animate response to make it looks smooth
|
||||||
|
function animateResponseText() {
|
||||||
|
if (finished || controller.signal.aborted) {
|
||||||
|
responseText += remainText;
|
||||||
|
console.log("[Response Animation] finished");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (remainText.length > 0) {
|
||||||
|
const fetchCount = Math.max(1, Math.round(remainText.length / 60));
|
||||||
|
const fetchText = remainText.slice(0, fetchCount);
|
||||||
|
responseText += fetchText;
|
||||||
|
remainText = remainText.slice(fetchCount);
|
||||||
|
options.onUpdate?.(responseText, fetchText);
|
||||||
|
}
|
||||||
|
|
||||||
|
requestAnimationFrame(animateResponseText);
|
||||||
|
}
|
||||||
|
|
||||||
|
// start animaion
|
||||||
|
animateResponseText();
|
||||||
|
|
||||||
const finish = () => {
|
const finish = () => {
|
||||||
if (!finished) {
|
if (!finished) {
|
||||||
options.onFinish(responseText);
|
|
||||||
finished = true;
|
finished = true;
|
||||||
|
options.onFinish(responseText + remainText);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -154,14 +197,19 @@ export class ChatGPTApi implements LLMApi {
|
|||||||
}
|
}
|
||||||
const text = msg.data;
|
const text = msg.data;
|
||||||
try {
|
try {
|
||||||
const json = JSON.parse(text);
|
const json = JSON.parse(text) as {
|
||||||
const delta = json.choices[0].delta.content;
|
choices: Array<{
|
||||||
|
delta: {
|
||||||
|
content: string;
|
||||||
|
};
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
const delta = json.choices[0]?.delta?.content;
|
||||||
if (delta) {
|
if (delta) {
|
||||||
responseText += delta;
|
remainText += delta;
|
||||||
options.onUpdate?.(responseText, delta);
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("[Request] parse error", text, msg);
|
console.error("[Request] parse error", text);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onclose() {
|
onclose() {
|
||||||
|
@@ -12,10 +12,16 @@ import { getClientConfig } from "../config/client";
|
|||||||
|
|
||||||
export function AuthPage() {
|
export function AuthPage() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const access = useAccessStore();
|
const accessStore = useAccessStore();
|
||||||
|
|
||||||
const goHome = () => navigate(Path.Home);
|
const goHome = () => navigate(Path.Home);
|
||||||
const resetAccessCode = () => access.updateCode(""); // Reset access code to empty string
|
const goChat = () => navigate(Path.Chat);
|
||||||
|
const resetAccessCode = () => {
|
||||||
|
accessStore.update((access) => {
|
||||||
|
access.openaiApiKey = "";
|
||||||
|
access.accessCode = "";
|
||||||
|
});
|
||||||
|
}; // Reset access code to empty string
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (getClientConfig()?.isApp) {
|
if (getClientConfig()?.isApp) {
|
||||||
@@ -37,22 +43,43 @@ export function AuthPage() {
|
|||||||
className={styles["auth-input"]}
|
className={styles["auth-input"]}
|
||||||
type="password"
|
type="password"
|
||||||
placeholder={Locale.Auth.Input}
|
placeholder={Locale.Auth.Input}
|
||||||
value={access.accessCode}
|
value={accessStore.accessCode}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
access.updateCode(e.currentTarget.value);
|
accessStore.update(
|
||||||
|
(access) => (access.accessCode = e.currentTarget.value),
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
{!accessStore.hideUserApiKey ? (
|
||||||
|
<>
|
||||||
|
<div className={styles["auth-tips"]}>{Locale.Auth.SubTips}</div>
|
||||||
|
<input
|
||||||
|
className={styles["auth-input"]}
|
||||||
|
type="password"
|
||||||
|
placeholder={Locale.Settings.Access.OpenAI.ApiKey.Placeholder}
|
||||||
|
value={accessStore.openaiApiKey}
|
||||||
|
onChange={(e) => {
|
||||||
|
accessStore.update(
|
||||||
|
(access) => (access.openaiApiKey = e.currentTarget.value),
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
|
||||||
<div className={styles["auth-actions"]}>
|
<div className={styles["auth-actions"]}>
|
||||||
<IconButton
|
<IconButton
|
||||||
text={Locale.Auth.Confirm}
|
text={Locale.Auth.Confirm}
|
||||||
type="primary"
|
type="primary"
|
||||||
onClick={goHome}
|
onClick={goChat}
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
text={Locale.Auth.Later}
|
||||||
|
onClick={() => {
|
||||||
|
resetAccessCode();
|
||||||
|
goHome();
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<IconButton text={Locale.Auth.Later} onClick={() => {
|
|
||||||
resetAccessCode();
|
|
||||||
goHome();
|
|
||||||
}} />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@@ -18,6 +18,7 @@ import { MaskAvatar } from "./mask";
|
|||||||
import { Mask } from "../store/mask";
|
import { Mask } from "../store/mask";
|
||||||
import { useRef, useEffect } from "react";
|
import { useRef, useEffect } from "react";
|
||||||
import { showConfirm } from "./ui-lib";
|
import { showConfirm } from "./ui-lib";
|
||||||
|
import { useMobileScreen } from "../utils";
|
||||||
|
|
||||||
export function ChatItem(props: {
|
export function ChatItem(props: {
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
@@ -60,7 +61,10 @@ export function ChatItem(props: {
|
|||||||
{props.narrow ? (
|
{props.narrow ? (
|
||||||
<div className={styles["chat-item-narrow"]}>
|
<div className={styles["chat-item-narrow"]}>
|
||||||
<div className={styles["chat-item-avatar"] + " no-dark"}>
|
<div className={styles["chat-item-avatar"] + " no-dark"}>
|
||||||
<MaskAvatar mask={props.mask} />
|
<MaskAvatar
|
||||||
|
avatar={props.mask.avatar}
|
||||||
|
model={props.mask.modelConfig.model}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles["chat-item-narrow-count"]}>
|
<div className={styles["chat-item-narrow-count"]}>
|
||||||
{props.count}
|
{props.count}
|
||||||
@@ -80,7 +84,11 @@ export function ChatItem(props: {
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
className={styles["chat-item-delete"]}
|
className={styles["chat-item-delete"]}
|
||||||
onClickCapture={props.onDelete}
|
onClickCapture={(e) => {
|
||||||
|
props.onDelete?.();
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<DeleteIcon />
|
<DeleteIcon />
|
||||||
</div>
|
</div>
|
||||||
@@ -101,6 +109,7 @@ export function ChatList(props: { narrow?: boolean }) {
|
|||||||
);
|
);
|
||||||
const chatStore = useChatStore();
|
const chatStore = useChatStore();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const isMobileScreen = useMobileScreen();
|
||||||
|
|
||||||
const onDragEnd: OnDragEndResponder = (result) => {
|
const onDragEnd: OnDragEndResponder = (result) => {
|
||||||
const { destination, source } = result;
|
const { destination, source } = result;
|
||||||
@@ -142,7 +151,7 @@ export function ChatList(props: { narrow?: boolean }) {
|
|||||||
}}
|
}}
|
||||||
onDelete={async () => {
|
onDelete={async () => {
|
||||||
if (
|
if (
|
||||||
!props.narrow ||
|
(!props.narrow && !isMobileScreen) ||
|
||||||
(await showConfirm(Locale.Home.DeleteChat))
|
(await showConfirm(Locale.Home.DeleteChat))
|
||||||
) {
|
) {
|
||||||
chatStore.deleteSession(i);
|
chatStore.deleteSession(i);
|
||||||
|
@@ -73,11 +73,10 @@ import {
|
|||||||
showPrompt,
|
showPrompt,
|
||||||
showToast,
|
showToast,
|
||||||
} from "./ui-lib";
|
} from "./ui-lib";
|
||||||
import { useLocation, useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import {
|
import {
|
||||||
CHAT_PAGE_SIZE,
|
CHAT_PAGE_SIZE,
|
||||||
LAST_INPUT_KEY,
|
LAST_INPUT_KEY,
|
||||||
MAX_RENDER_MSG_COUNT,
|
|
||||||
Path,
|
Path,
|
||||||
REQUEST_TIMEOUT_MS,
|
REQUEST_TIMEOUT_MS,
|
||||||
UNFINISHED_INPUT,
|
UNFINISHED_INPUT,
|
||||||
@@ -89,6 +88,7 @@ import { ChatCommandPrefix, useChatCommand, useCommand } from "../command";
|
|||||||
import { prettyObject } from "../utils/format";
|
import { prettyObject } from "../utils/format";
|
||||||
import { ExportMessageModal } from "./exporter";
|
import { ExportMessageModal } from "./exporter";
|
||||||
import { getClientConfig } from "../config/client";
|
import { getClientConfig } from "../config/client";
|
||||||
|
import { useAllModels } from "../utils/hooks";
|
||||||
|
|
||||||
const Markdown = dynamic(async () => (await import("./markdown")).Markdown, {
|
const Markdown = dynamic(async () => (await import("./markdown")).Markdown, {
|
||||||
loading: () => <LoadingIcon />,
|
loading: () => <LoadingIcon />,
|
||||||
@@ -144,6 +144,7 @@ export function SessionConfigModel(props: { onClose: () => void }) {
|
|||||||
extraListItems={
|
extraListItems={
|
||||||
session.mask.modelConfig.sendMemory ? (
|
session.mask.modelConfig.sendMemory ? (
|
||||||
<ListItem
|
<ListItem
|
||||||
|
className="copyable"
|
||||||
title={`${Locale.Memory.Title} (${session.lastSummarizeIndex} of ${session.messages.length})`}
|
title={`${Locale.Memory.Title} (${session.lastSummarizeIndex} of ${session.messages.length})`}
|
||||||
subTitle={session.memoryPrompt || Locale.Memory.EmptyContent}
|
subTitle={session.memoryPrompt || Locale.Memory.EmptyContent}
|
||||||
></ListItem>
|
></ListItem>
|
||||||
@@ -430,16 +431,26 @@ export function ChatActions(props: {
|
|||||||
|
|
||||||
// switch model
|
// switch model
|
||||||
const currentModel = chatStore.currentSession().mask.modelConfig.model;
|
const currentModel = chatStore.currentSession().mask.modelConfig.model;
|
||||||
|
const allModels = useAllModels();
|
||||||
const models = useMemo(
|
const models = useMemo(
|
||||||
() =>
|
() => allModels.filter((m) => m.available),
|
||||||
config
|
[allModels],
|
||||||
.allModels()
|
|
||||||
.filter((m) => m.available)
|
|
||||||
.map((m) => m.name),
|
|
||||||
[config],
|
|
||||||
);
|
);
|
||||||
const [showModelSelector, setShowModelSelector] = useState(false);
|
const [showModelSelector, setShowModelSelector] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// if current model is not available
|
||||||
|
// switch to first available model
|
||||||
|
const isUnavaliableModel = !models.some((m) => m.name === currentModel);
|
||||||
|
if (isUnavaliableModel && models.length > 0) {
|
||||||
|
const nextModel = models[0].name as ModelType;
|
||||||
|
chatStore.updateCurrentSession(
|
||||||
|
(session) => (session.mask.modelConfig.model = nextModel),
|
||||||
|
);
|
||||||
|
showToast(nextModel);
|
||||||
|
}
|
||||||
|
}, [chatStore, currentModel, models]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles["chat-input-actions"]}>
|
<div className={styles["chat-input-actions"]}>
|
||||||
{couldStop && (
|
{couldStop && (
|
||||||
@@ -519,8 +530,8 @@ export function ChatActions(props: {
|
|||||||
<Selector
|
<Selector
|
||||||
defaultSelectedValue={currentModel}
|
defaultSelectedValue={currentModel}
|
||||||
items={models.map((m) => ({
|
items={models.map((m) => ({
|
||||||
title: m,
|
title: m.displayName,
|
||||||
value: m,
|
value: m.name,
|
||||||
}))}
|
}))}
|
||||||
onClose={() => setShowModelSelector(false)}
|
onClose={() => setShowModelSelector(false)}
|
||||||
onSelection={(s) => {
|
onSelection={(s) => {
|
||||||
@@ -937,7 +948,7 @@ function _Chat() {
|
|||||||
const isTouchTopEdge = e.scrollTop <= edgeThreshold;
|
const isTouchTopEdge = e.scrollTop <= edgeThreshold;
|
||||||
const isTouchBottomEdge = bottomHeight >= e.scrollHeight - edgeThreshold;
|
const isTouchBottomEdge = bottomHeight >= e.scrollHeight - edgeThreshold;
|
||||||
const isHitBottom =
|
const isHitBottom =
|
||||||
bottomHeight >= e.scrollHeight - (isMobileScreen ? 0 : 10);
|
bottomHeight >= e.scrollHeight - (isMobileScreen ? 4 : 10);
|
||||||
|
|
||||||
const prevPageMsgIndex = msgRenderIndex - CHAT_PAGE_SIZE;
|
const prevPageMsgIndex = msgRenderIndex - CHAT_PAGE_SIZE;
|
||||||
const nextPageMsgIndex = msgRenderIndex + CHAT_PAGE_SIZE;
|
const nextPageMsgIndex = msgRenderIndex + CHAT_PAGE_SIZE;
|
||||||
@@ -976,14 +987,17 @@ function _Chat() {
|
|||||||
doSubmit(text);
|
doSubmit(text);
|
||||||
},
|
},
|
||||||
code: (text) => {
|
code: (text) => {
|
||||||
|
if (accessStore.disableFastLink) return;
|
||||||
console.log("[Command] got code from url: ", text);
|
console.log("[Command] got code from url: ", text);
|
||||||
showConfirm(Locale.URLCommand.Code + `code = ${text}`).then((res) => {
|
showConfirm(Locale.URLCommand.Code + `code = ${text}`).then((res) => {
|
||||||
if (res) {
|
if (res) {
|
||||||
accessStore.updateCode(text);
|
accessStore.update((access) => (access.accessCode = text));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
settings: (text) => {
|
settings: (text) => {
|
||||||
|
if (accessStore.disableFastLink) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const payload = JSON.parse(text) as {
|
const payload = JSON.parse(text) as {
|
||||||
key?: string;
|
key?: string;
|
||||||
@@ -999,10 +1013,12 @@ function _Chat() {
|
|||||||
).then((res) => {
|
).then((res) => {
|
||||||
if (!res) return;
|
if (!res) return;
|
||||||
if (payload.key) {
|
if (payload.key) {
|
||||||
accessStore.updateToken(payload.key);
|
accessStore.update(
|
||||||
|
(access) => (access.openaiApiKey = payload.key!),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (payload.url) {
|
if (payload.url) {
|
||||||
accessStore.updateOpenAiUrl(payload.url);
|
accessStore.update((access) => (access.openaiUrl = payload.url!));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1155,7 +1171,18 @@ function _Chat() {
|
|||||||
{isUser ? (
|
{isUser ? (
|
||||||
<Avatar avatar={config.avatar} />
|
<Avatar avatar={config.avatar} />
|
||||||
) : (
|
) : (
|
||||||
<MaskAvatar mask={session.mask} />
|
<>
|
||||||
|
{["system"].includes(message.role) ? (
|
||||||
|
<Avatar avatar="2699-fe0f" />
|
||||||
|
) : (
|
||||||
|
<MaskAvatar
|
||||||
|
avatar={session.mask.avatar}
|
||||||
|
model={
|
||||||
|
message.model || session.mask.modelConfig.model
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@@ -186,7 +186,8 @@
|
|||||||
box-shadow: var(--card-shadow);
|
box-shadow: var(--card-shadow);
|
||||||
border: var(--border-in-light);
|
border: var(--border-in-light);
|
||||||
|
|
||||||
*:not(li) {
|
code,
|
||||||
|
pre {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
/* eslint-disable @next/next/no-img-element */
|
/* eslint-disable @next/next/no-img-element */
|
||||||
import { ChatMessage, useAppConfig, useChatStore } from "../store";
|
import { ChatMessage, ModelType, useAppConfig, useChatStore } from "../store";
|
||||||
import Locale from "../locales";
|
import Locale from "../locales";
|
||||||
import styles from "./exporter.module.scss";
|
import styles from "./exporter.module.scss";
|
||||||
import {
|
import {
|
||||||
@@ -27,7 +27,7 @@ import { Avatar } from "./emoji";
|
|||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
import NextImage from "next/image";
|
import NextImage from "next/image";
|
||||||
|
|
||||||
import { toBlob, toJpeg, toPng } from "html-to-image";
|
import { toBlob, toPng } from "html-to-image";
|
||||||
import { DEFAULT_MASK_AVATAR } from "../store/mask";
|
import { DEFAULT_MASK_AVATAR } from "../store/mask";
|
||||||
import { api } from "../client/api";
|
import { api } from "../client/api";
|
||||||
import { prettyObject } from "../utils/format";
|
import { prettyObject } from "../utils/format";
|
||||||
@@ -41,7 +41,22 @@ const Markdown = dynamic(async () => (await import("./markdown")).Markdown, {
|
|||||||
export function ExportMessageModal(props: { onClose: () => void }) {
|
export function ExportMessageModal(props: { onClose: () => void }) {
|
||||||
return (
|
return (
|
||||||
<div className="modal-mask">
|
<div className="modal-mask">
|
||||||
<Modal title={Locale.Export.Title} onClose={props.onClose}>
|
<Modal
|
||||||
|
title={Locale.Export.Title}
|
||||||
|
onClose={props.onClose}
|
||||||
|
footer={
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
textAlign: "center",
|
||||||
|
fontSize: 14,
|
||||||
|
opacity: 0.5,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Locale.Exporter.Description.Title}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
<div style={{ minHeight: "40vh" }}>
|
<div style={{ minHeight: "40vh" }}>
|
||||||
<MessageExporter />
|
<MessageExporter />
|
||||||
</div>
|
</div>
|
||||||
@@ -149,7 +164,7 @@ export function MessageExporter() {
|
|||||||
if (exportConfig.includeContext) {
|
if (exportConfig.includeContext) {
|
||||||
ret.push(...session.mask.context);
|
ret.push(...session.mask.context);
|
||||||
}
|
}
|
||||||
ret.push(...session.messages.filter((m, i) => selection.has(m.id)));
|
ret.push(...session.messages.filter((m) => selection.has(m.id)));
|
||||||
return ret;
|
return ret;
|
||||||
}, [
|
}, [
|
||||||
exportConfig.includeContext,
|
exportConfig.includeContext,
|
||||||
@@ -260,7 +275,8 @@ export function RenderExport(props: {
|
|||||||
});
|
});
|
||||||
|
|
||||||
props.onRender(renderMsgs);
|
props.onRender(renderMsgs);
|
||||||
});
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={domRef}>
|
<div ref={domRef}>
|
||||||
@@ -433,25 +449,55 @@ export function ImagePreviewer(props: {
|
|||||||
|
|
||||||
const isMobile = useMobileScreen();
|
const isMobile = useMobileScreen();
|
||||||
|
|
||||||
const download = () => {
|
const download = async () => {
|
||||||
showToast(Locale.Export.Image.Toast);
|
showToast(Locale.Export.Image.Toast);
|
||||||
const dom = previewRef.current;
|
const dom = previewRef.current;
|
||||||
if (!dom) return;
|
if (!dom) return;
|
||||||
toPng(dom)
|
|
||||||
.then((blob) => {
|
|
||||||
if (!blob) return;
|
|
||||||
|
|
||||||
if (isMobile || getClientConfig()?.isApp) {
|
const isApp = getClientConfig()?.isApp;
|
||||||
showImageModal(blob);
|
|
||||||
|
try {
|
||||||
|
const blob = await toPng(dom);
|
||||||
|
if (!blob) return;
|
||||||
|
|
||||||
|
if (isMobile || (isApp && window.__TAURI__)) {
|
||||||
|
if (isApp && window.__TAURI__) {
|
||||||
|
const result = await window.__TAURI__.dialog.save({
|
||||||
|
defaultPath: `${props.topic}.png`,
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
name: "PNG Files",
|
||||||
|
extensions: ["png"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "All Files",
|
||||||
|
extensions: ["*"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result !== null) {
|
||||||
|
const response = await fetch(blob);
|
||||||
|
const buffer = await response.arrayBuffer();
|
||||||
|
const uint8Array = new Uint8Array(buffer);
|
||||||
|
await window.__TAURI__.fs.writeBinaryFile(result, uint8Array);
|
||||||
|
showToast(Locale.Download.Success);
|
||||||
|
} else {
|
||||||
|
showToast(Locale.Download.Failed);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
const link = document.createElement("a");
|
showImageModal(blob);
|
||||||
link.download = `${props.topic}.png`;
|
|
||||||
link.href = blob;
|
|
||||||
link.click();
|
|
||||||
refreshPreview();
|
|
||||||
}
|
}
|
||||||
})
|
} else {
|
||||||
.catch((e) => console.log("[Export Image] ", e));
|
const link = document.createElement("a");
|
||||||
|
link.download = `${props.topic}.png`;
|
||||||
|
link.href = blob;
|
||||||
|
link.click();
|
||||||
|
refreshPreview();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
showToast(Locale.Download.Failed);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const refreshPreview = () => {
|
const refreshPreview = () => {
|
||||||
@@ -574,8 +620,6 @@ export function MarkdownPreviewer(props: {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// modified by BackTrackZ now it's looks better
|
|
||||||
|
|
||||||
export function JsonPreviewer(props: {
|
export function JsonPreviewer(props: {
|
||||||
messages: ChatMessage[];
|
messages: ChatMessage[];
|
||||||
topic: string;
|
topic: string;
|
||||||
|
@@ -115,7 +115,10 @@ const loadAsyncGoogleFont = () => {
|
|||||||
getClientConfig()?.buildMode === "export" ? remoteFontUrl : proxyFontUrl;
|
getClientConfig()?.buildMode === "export" ? remoteFontUrl : proxyFontUrl;
|
||||||
linkEl.rel = "stylesheet";
|
linkEl.rel = "stylesheet";
|
||||||
linkEl.href =
|
linkEl.href =
|
||||||
googleFontUrl + "/css2?family=" + encodeURIComponent("Noto Sans:wght@300;400;700;900") + "&display=swap";
|
googleFontUrl +
|
||||||
|
"/css2?family=" +
|
||||||
|
encodeURIComponent("Noto Sans:wght@300;400;700;900") +
|
||||||
|
"&display=swap";
|
||||||
document.head.appendChild(linkEl);
|
document.head.appendChild(linkEl);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -125,6 +128,7 @@ function Screen() {
|
|||||||
const isHome = location.pathname === Path.Home;
|
const isHome = location.pathname === Path.Home;
|
||||||
const isAuth = location.pathname === Path.Auth;
|
const isAuth = location.pathname === Path.Auth;
|
||||||
const isMobileScreen = useMobileScreen();
|
const isMobileScreen = useMobileScreen();
|
||||||
|
const shouldTightBorder = getClientConfig()?.isApp || (config.tightBorder && !isMobileScreen);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadAsyncGoogleFont();
|
loadAsyncGoogleFont();
|
||||||
@@ -134,11 +138,9 @@ function Screen() {
|
|||||||
<div
|
<div
|
||||||
className={
|
className={
|
||||||
styles.container +
|
styles.container +
|
||||||
` ${
|
` ${shouldTightBorder ? styles["tight-container"] : styles.container} ${
|
||||||
config.tightBorder && !isMobileScreen
|
getLang() === "ar" ? styles["rtl-screen"] : ""
|
||||||
? styles["tight-container"]
|
}`
|
||||||
: styles.container
|
|
||||||
} ${getLang() === "ar" ? styles["rtl-screen"] : ""}`
|
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{isAuth ? (
|
{isAuth ? (
|
||||||
|
@@ -5,13 +5,13 @@ import RemarkBreaks from "remark-breaks";
|
|||||||
import RehypeKatex from "rehype-katex";
|
import RehypeKatex from "rehype-katex";
|
||||||
import RemarkGfm from "remark-gfm";
|
import RemarkGfm from "remark-gfm";
|
||||||
import RehypeHighlight from "rehype-highlight";
|
import RehypeHighlight from "rehype-highlight";
|
||||||
import { useRef, useState, RefObject, useEffect } from "react";
|
import { useRef, useState, RefObject, useEffect, useMemo } from "react";
|
||||||
import { copyToClipboard } from "../utils";
|
import { copyToClipboard } from "../utils";
|
||||||
import mermaid from "mermaid";
|
import mermaid from "mermaid";
|
||||||
|
|
||||||
import LoadingIcon from "../icons/three-dots.svg";
|
import LoadingIcon from "../icons/three-dots.svg";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useDebouncedCallback, useThrottledCallback } from "use-debounce";
|
import { useDebouncedCallback } from "use-debounce";
|
||||||
import { showImageModal } from "./ui-lib";
|
import { showImageModal } from "./ui-lib";
|
||||||
|
|
||||||
export function Mermaid(props: { code: string }) {
|
export function Mermaid(props: { code: string }) {
|
||||||
@@ -99,7 +99,29 @@ export function PreCode(props: { children: any }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function escapeDollarNumber(text: string) {
|
||||||
|
let escapedText = "";
|
||||||
|
|
||||||
|
for (let i = 0; i < text.length; i += 1) {
|
||||||
|
let char = text[i];
|
||||||
|
const nextChar = text[i + 1] || " ";
|
||||||
|
|
||||||
|
if (char === "$" && nextChar >= "0" && nextChar <= "9") {
|
||||||
|
char = "\\$";
|
||||||
|
}
|
||||||
|
|
||||||
|
escapedText += char;
|
||||||
|
}
|
||||||
|
|
||||||
|
return escapedText;
|
||||||
|
}
|
||||||
|
|
||||||
function _MarkDownContent(props: { content: string }) {
|
function _MarkDownContent(props: { content: string }) {
|
||||||
|
const escapedContent = useMemo(
|
||||||
|
() => escapeDollarNumber(props.content),
|
||||||
|
[props.content],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ReactMarkdown
|
<ReactMarkdown
|
||||||
remarkPlugins={[RemarkMath, RemarkGfm, RemarkBreaks]}
|
remarkPlugins={[RemarkMath, RemarkGfm, RemarkBreaks]}
|
||||||
@@ -124,7 +146,7 @@ function _MarkDownContent(props: { content: string }) {
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{props.content}
|
{escapedContent}
|
||||||
</ReactMarkdown>
|
</ReactMarkdown>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -18,6 +18,7 @@ import {
|
|||||||
ChatMessage,
|
ChatMessage,
|
||||||
createMessage,
|
createMessage,
|
||||||
ModelConfig,
|
ModelConfig,
|
||||||
|
ModelType,
|
||||||
useAppConfig,
|
useAppConfig,
|
||||||
useChatStore,
|
useChatStore,
|
||||||
} from "../store";
|
} from "../store";
|
||||||
@@ -58,11 +59,11 @@ function reorder<T>(list: T[], startIndex: number, endIndex: number): T[] {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function MaskAvatar(props: { mask: Mask }) {
|
export function MaskAvatar(props: { avatar: string; model?: ModelType }) {
|
||||||
return props.mask.avatar !== DEFAULT_MASK_AVATAR ? (
|
return props.avatar !== DEFAULT_MASK_AVATAR ? (
|
||||||
<Avatar avatar={props.mask.avatar} />
|
<Avatar avatar={props.avatar} />
|
||||||
) : (
|
) : (
|
||||||
<Avatar model={props.mask.modelConfig.model} />
|
<Avatar model={props.model} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,7 +124,10 @@ export function MaskConfig(props: {
|
|||||||
onClick={() => setShowPicker(true)}
|
onClick={() => setShowPicker(true)}
|
||||||
style={{ cursor: "pointer" }}
|
style={{ cursor: "pointer" }}
|
||||||
>
|
>
|
||||||
<MaskAvatar mask={props.mask} />
|
<MaskAvatar
|
||||||
|
avatar={props.mask.avatar}
|
||||||
|
model={props.mask.modelConfig.model}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Popover>
|
</Popover>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
@@ -393,11 +397,13 @@ export function MaskPage() {
|
|||||||
const [searchText, setSearchText] = useState("");
|
const [searchText, setSearchText] = useState("");
|
||||||
const masks = searchText.length > 0 ? searchMasks : allMasks;
|
const masks = searchText.length > 0 ? searchMasks : allMasks;
|
||||||
|
|
||||||
// simple search, will refactor later
|
// refactored already, now it accurate
|
||||||
const onSearch = (text: string) => {
|
const onSearch = (text: string) => {
|
||||||
setSearchText(text);
|
setSearchText(text);
|
||||||
if (text.length > 0) {
|
if (text.length > 0) {
|
||||||
const result = allMasks.filter((m) => m.name.includes(text));
|
const result = allMasks.filter((m) =>
|
||||||
|
m.name.toLowerCase().includes(text.toLowerCase()),
|
||||||
|
);
|
||||||
setSearchMasks(result);
|
setSearchMasks(result);
|
||||||
} else {
|
} else {
|
||||||
setSearchMasks(allMasks);
|
setSearchMasks(allMasks);
|
||||||
@@ -521,7 +527,7 @@ export function MaskPage() {
|
|||||||
<div className={styles["mask-item"]} key={m.id}>
|
<div className={styles["mask-item"]} key={m.id}>
|
||||||
<div className={styles["mask-header"]}>
|
<div className={styles["mask-header"]}>
|
||||||
<div className={styles["mask-icon"]}>
|
<div className={styles["mask-icon"]}>
|
||||||
<MaskAvatar mask={m} />
|
<MaskAvatar avatar={m.avatar} model={m.modelConfig.model} />
|
||||||
</div>
|
</div>
|
||||||
<div className={styles["mask-title"]}>
|
<div className={styles["mask-title"]}>
|
||||||
<div className={styles["mask-name"]}>{m.name}</div>
|
<div className={styles["mask-name"]}>{m.name}</div>
|
||||||
|
@@ -58,8 +58,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.body {
|
.body {
|
||||||
flex-grow: 1;
|
flex: 1;
|
||||||
max-width: calc(100% - 40px);
|
max-width: calc(100% - 80px);
|
||||||
|
|
||||||
.date {
|
.date {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
@@ -71,6 +71,12 @@
|
|||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.checkbox {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
import { ChatMessage, useAppConfig, useChatStore } from "../store";
|
import { ChatMessage, useAppConfig, useChatStore } from "../store";
|
||||||
import { Updater } from "../typing";
|
import { Updater } from "../typing";
|
||||||
import { IconButton } from "./button";
|
import { IconButton } from "./button";
|
||||||
@@ -73,11 +73,23 @@ export function MessageSelector(props: {
|
|||||||
const chatStore = useChatStore();
|
const chatStore = useChatStore();
|
||||||
const session = chatStore.currentSession();
|
const session = chatStore.currentSession();
|
||||||
const isValid = (m: ChatMessage) => m.content && !m.isError && !m.streaming;
|
const isValid = (m: ChatMessage) => m.content && !m.isError && !m.streaming;
|
||||||
const messages = session.messages.filter(
|
const allMessages = useMemo(() => {
|
||||||
(m, i) =>
|
let startIndex = Math.max(0, session.clearContextIndex ?? 0);
|
||||||
m.id && // message must have id
|
if (startIndex === session.messages.length - 1) {
|
||||||
isValid(m) &&
|
startIndex = 0;
|
||||||
(i >= session.messages.length - 1 || isValid(session.messages[i + 1])),
|
}
|
||||||
|
return session.messages.slice(startIndex);
|
||||||
|
}, [session.messages, session.clearContextIndex]);
|
||||||
|
|
||||||
|
const messages = useMemo(
|
||||||
|
() =>
|
||||||
|
allMessages.filter(
|
||||||
|
(m, i) =>
|
||||||
|
m.id && // message must have id
|
||||||
|
isValid(m) &&
|
||||||
|
(i >= allMessages.length - 1 || isValid(allMessages[i + 1])),
|
||||||
|
),
|
||||||
|
[allMessages],
|
||||||
);
|
);
|
||||||
const messageCount = messages.length;
|
const messageCount = messages.length;
|
||||||
const config = useAppConfig();
|
const config = useAppConfig();
|
||||||
@@ -176,6 +188,8 @@ export function MessageSelector(props: {
|
|||||||
<div className={styles["messages"]}>
|
<div className={styles["messages"]}>
|
||||||
{messages.map((m, i) => {
|
{messages.map((m, i) => {
|
||||||
if (!isInSearchResult(m.id!)) return null;
|
if (!isInSearchResult(m.id!)) return null;
|
||||||
|
const id = m.id ?? i;
|
||||||
|
const isSelected = props.selection.has(id);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -185,7 +199,6 @@ export function MessageSelector(props: {
|
|||||||
key={i}
|
key={i}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
props.updateSelection((selection) => {
|
props.updateSelection((selection) => {
|
||||||
const id = m.id ?? i;
|
|
||||||
selection.has(id) ? selection.delete(id) : selection.add(id);
|
selection.has(id) ? selection.delete(id) : selection.add(id);
|
||||||
});
|
});
|
||||||
onClickIndex(i);
|
onClickIndex(i);
|
||||||
@@ -195,7 +208,10 @@ export function MessageSelector(props: {
|
|||||||
{m.role === "user" ? (
|
{m.role === "user" ? (
|
||||||
<Avatar avatar={config.avatar}></Avatar>
|
<Avatar avatar={config.avatar}></Avatar>
|
||||||
) : (
|
) : (
|
||||||
<MaskAvatar mask={session.mask} />
|
<MaskAvatar
|
||||||
|
avatar={session.mask.avatar}
|
||||||
|
model={m.model || session.mask.modelConfig.model}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className={styles["body"]}>
|
<div className={styles["body"]}>
|
||||||
@@ -206,6 +222,10 @@ export function MessageSelector(props: {
|
|||||||
{m.content}
|
{m.content}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className={styles["checkbox"]}>
|
||||||
|
<input type="checkbox" checked={isSelected}></input>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
@@ -1,14 +1,15 @@
|
|||||||
import { ModalConfigValidator, ModelConfig, useAppConfig } from "../store";
|
import { ModalConfigValidator, ModelConfig } from "../store";
|
||||||
|
|
||||||
import Locale from "../locales";
|
import Locale from "../locales";
|
||||||
import { InputRange } from "./input-range";
|
import { InputRange } from "./input-range";
|
||||||
import { ListItem, Select } from "./ui-lib";
|
import { ListItem, Select } from "./ui-lib";
|
||||||
|
import { useAllModels } from "../utils/hooks";
|
||||||
|
|
||||||
export function ModelConfigList(props: {
|
export function ModelConfigList(props: {
|
||||||
modelConfig: ModelConfig;
|
modelConfig: ModelConfig;
|
||||||
updateConfig: (updater: (config: ModelConfig) => void) => void;
|
updateConfig: (updater: (config: ModelConfig) => void) => void;
|
||||||
}) {
|
}) {
|
||||||
const config = useAppConfig();
|
const allModels = useAllModels();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -24,11 +25,13 @@ export function ModelConfigList(props: {
|
|||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{config.allModels().map((v, i) => (
|
{allModels
|
||||||
<option value={v.name} key={i} disabled={!v.available}>
|
.filter((v) => v.available)
|
||||||
{v.name}
|
.map((v, i) => (
|
||||||
</option>
|
<option value={v.name} key={i}>
|
||||||
))}
|
{v.displayName}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
<ListItem
|
<ListItem
|
||||||
@@ -75,8 +78,8 @@ export function ModelConfigList(props: {
|
|||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
min={100}
|
min={1024}
|
||||||
max={100000}
|
max={512000}
|
||||||
value={props.modelConfig.max_tokens}
|
value={props.modelConfig.max_tokens}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
props.updateConfig(
|
props.updateConfig(
|
||||||
|
@@ -17,21 +17,13 @@ import { useCommand } from "../command";
|
|||||||
import { showConfirm } from "./ui-lib";
|
import { showConfirm } from "./ui-lib";
|
||||||
import { BUILTIN_MASK_STORE } from "../masks";
|
import { BUILTIN_MASK_STORE } from "../masks";
|
||||||
|
|
||||||
function getIntersectionArea(aRect: DOMRect, bRect: DOMRect) {
|
|
||||||
const xmin = Math.max(aRect.x, bRect.x);
|
|
||||||
const xmax = Math.min(aRect.x + aRect.width, bRect.x + bRect.width);
|
|
||||||
const ymin = Math.max(aRect.y, bRect.y);
|
|
||||||
const ymax = Math.min(aRect.y + aRect.height, bRect.y + bRect.height);
|
|
||||||
const width = xmax - xmin;
|
|
||||||
const height = ymax - ymin;
|
|
||||||
const intersectionArea = width < 0 || height < 0 ? 0 : width * height;
|
|
||||||
return intersectionArea;
|
|
||||||
}
|
|
||||||
|
|
||||||
function MaskItem(props: { mask: Mask; onClick?: () => void }) {
|
function MaskItem(props: { mask: Mask; onClick?: () => void }) {
|
||||||
return (
|
return (
|
||||||
<div className={styles["mask"]} onClick={props.onClick}>
|
<div className={styles["mask"]} onClick={props.onClick}>
|
||||||
<MaskAvatar mask={props.mask} />
|
<MaskAvatar
|
||||||
|
avatar={props.mask.avatar}
|
||||||
|
model={props.mask.modelConfig.model}
|
||||||
|
/>
|
||||||
<div className={styles["mask-name"] + " one-line"}>{props.mask.name}</div>
|
<div className={styles["mask-name"] + " one-line"}>{props.mask.name}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@@ -50,7 +50,16 @@ import Locale, {
|
|||||||
} from "../locales";
|
} from "../locales";
|
||||||
import { copyToClipboard } from "../utils";
|
import { copyToClipboard } from "../utils";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { Path, RELEASE_URL, STORAGE_KEY, UPDATE_URL } from "../constant";
|
import {
|
||||||
|
Azure,
|
||||||
|
OPENAI_BASE_URL,
|
||||||
|
Path,
|
||||||
|
RELEASE_URL,
|
||||||
|
STORAGE_KEY,
|
||||||
|
ServiceProvider,
|
||||||
|
SlotID,
|
||||||
|
UPDATE_URL,
|
||||||
|
} from "../constant";
|
||||||
import { Prompt, SearchService, usePromptStore } from "../store/prompt";
|
import { Prompt, SearchService, usePromptStore } from "../store/prompt";
|
||||||
import { ErrorBoundary } from "./error";
|
import { ErrorBoundary } from "./error";
|
||||||
import { InputRange } from "./input-range";
|
import { InputRange } from "./input-range";
|
||||||
@@ -571,13 +580,27 @@ export function Settings() {
|
|||||||
console.log("[Update] remote version ", updateStore.remoteVersion);
|
console.log("[Update] remote version ", updateStore.remoteVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const accessStore = useAccessStore();
|
||||||
|
const shouldHideBalanceQuery = useMemo(() => {
|
||||||
|
const isOpenAiUrl = accessStore.openaiUrl.includes(OPENAI_BASE_URL);
|
||||||
|
return (
|
||||||
|
accessStore.hideBalanceQuery ||
|
||||||
|
isOpenAiUrl ||
|
||||||
|
accessStore.provider === ServiceProvider.Azure
|
||||||
|
);
|
||||||
|
}, [
|
||||||
|
accessStore.hideBalanceQuery,
|
||||||
|
accessStore.openaiUrl,
|
||||||
|
accessStore.provider,
|
||||||
|
]);
|
||||||
|
|
||||||
const usage = {
|
const usage = {
|
||||||
used: updateStore.used,
|
used: updateStore.used,
|
||||||
subscription: updateStore.subscription,
|
subscription: updateStore.subscription,
|
||||||
};
|
};
|
||||||
const [loadingUsage, setLoadingUsage] = useState(false);
|
const [loadingUsage, setLoadingUsage] = useState(false);
|
||||||
function checkUsage(force = false) {
|
function checkUsage(force = false) {
|
||||||
if (accessStore.hideBalanceQuery) {
|
if (shouldHideBalanceQuery) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -587,7 +610,6 @@ export function Settings() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const accessStore = useAccessStore();
|
|
||||||
const enabledAccessControl = useMemo(
|
const enabledAccessControl = useMemo(
|
||||||
() => accessStore.enabledAccessControl(),
|
() => accessStore.enabledAccessControl(),
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
@@ -613,6 +635,11 @@ export function Settings() {
|
|||||||
navigate(Path.Home);
|
navigate(Path.Home);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
if (clientConfig?.isApp) { // Force to set custom endpoint to true if it's app
|
||||||
|
accessStore.update((state) => {
|
||||||
|
state.useCustomConfig = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
document.addEventListener("keydown", keydownEvent);
|
document.addEventListener("keydown", keydownEvent);
|
||||||
return () => {
|
return () => {
|
||||||
document.removeEventListener("keydown", keydownEvent);
|
document.removeEventListener("keydown", keydownEvent);
|
||||||
@@ -753,7 +780,7 @@ export function Settings() {
|
|||||||
title={`${config.fontSize ?? 14}px`}
|
title={`${config.fontSize ?? 14}px`}
|
||||||
value={config.fontSize}
|
value={config.fontSize}
|
||||||
min="12"
|
min="12"
|
||||||
max="18"
|
max="40"
|
||||||
step="1"
|
step="1"
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
updateConfig(
|
updateConfig(
|
||||||
@@ -866,57 +893,176 @@ export function Settings() {
|
|||||||
</ListItem>
|
</ListItem>
|
||||||
</List>
|
</List>
|
||||||
|
|
||||||
<List>
|
<List id={SlotID.CustomModel}>
|
||||||
{showAccessCode ? (
|
{showAccessCode && (
|
||||||
<ListItem
|
<ListItem
|
||||||
title={Locale.Settings.AccessCode.Title}
|
title={Locale.Settings.Access.AccessCode.Title}
|
||||||
subTitle={Locale.Settings.AccessCode.SubTitle}
|
subTitle={Locale.Settings.Access.AccessCode.SubTitle}
|
||||||
>
|
>
|
||||||
<PasswordInput
|
<PasswordInput
|
||||||
value={accessStore.accessCode}
|
value={accessStore.accessCode}
|
||||||
type="text"
|
type="text"
|
||||||
placeholder={Locale.Settings.AccessCode.Placeholder}
|
placeholder={Locale.Settings.Access.AccessCode.Placeholder}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
accessStore.updateCode(e.currentTarget.value);
|
accessStore.update(
|
||||||
|
(access) => (access.accessCode = e.currentTarget.value),
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
) : (
|
|
||||||
<></>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!accessStore.hideUserApiKey ? (
|
{!accessStore.hideUserApiKey && (
|
||||||
<>
|
<>
|
||||||
<ListItem
|
{
|
||||||
title={Locale.Settings.Endpoint.Title}
|
// Conditionally render the following ListItem based on clientConfig.isApp
|
||||||
subTitle={Locale.Settings.Endpoint.SubTitle}
|
!clientConfig?.isApp && ( // only show if isApp is false
|
||||||
>
|
<ListItem
|
||||||
<input
|
title={Locale.Settings.Access.CustomEndpoint.Title}
|
||||||
type="text"
|
subTitle={Locale.Settings.Access.CustomEndpoint.SubTitle}
|
||||||
value={accessStore.openaiUrl}
|
>
|
||||||
placeholder="https://api.openai.com/"
|
<input
|
||||||
onChange={(e) =>
|
type="checkbox"
|
||||||
accessStore.updateOpenAiUrl(e.currentTarget.value)
|
checked={accessStore.useCustomConfig}
|
||||||
}
|
onChange={(e) =>
|
||||||
></input>
|
accessStore.update(
|
||||||
</ListItem>
|
(access) =>
|
||||||
<ListItem
|
(access.useCustomConfig = e.currentTarget.checked),
|
||||||
title={Locale.Settings.Token.Title}
|
)
|
||||||
subTitle={Locale.Settings.Token.SubTitle}
|
}
|
||||||
>
|
></input>
|
||||||
<PasswordInput
|
</ListItem>
|
||||||
value={accessStore.token}
|
)
|
||||||
type="text"
|
}
|
||||||
placeholder={Locale.Settings.Token.Placeholder}
|
{accessStore.useCustomConfig && (
|
||||||
onChange={(e) => {
|
<>
|
||||||
accessStore.updateToken(e.currentTarget.value);
|
<ListItem
|
||||||
}}
|
title={Locale.Settings.Access.Provider.Title}
|
||||||
/>
|
subTitle={Locale.Settings.Access.Provider.SubTitle}
|
||||||
</ListItem>
|
>
|
||||||
</>
|
<Select
|
||||||
) : null}
|
value={accessStore.provider}
|
||||||
|
onChange={(e) => {
|
||||||
|
accessStore.update(
|
||||||
|
(access) =>
|
||||||
|
(access.provider = e.target
|
||||||
|
.value as ServiceProvider),
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Object.entries(ServiceProvider).map(([k, v]) => (
|
||||||
|
<option value={v} key={k}>
|
||||||
|
{k}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</ListItem>
|
||||||
|
|
||||||
{!accessStore.hideBalanceQuery ? (
|
{accessStore.provider === "OpenAI" ? (
|
||||||
|
<>
|
||||||
|
<ListItem
|
||||||
|
title={Locale.Settings.Access.OpenAI.Endpoint.Title}
|
||||||
|
subTitle={
|
||||||
|
Locale.Settings.Access.OpenAI.Endpoint.SubTitle
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={accessStore.openaiUrl}
|
||||||
|
placeholder={OPENAI_BASE_URL}
|
||||||
|
onChange={(e) =>
|
||||||
|
accessStore.update(
|
||||||
|
(access) =>
|
||||||
|
(access.openaiUrl = e.currentTarget.value),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
></input>
|
||||||
|
</ListItem>
|
||||||
|
<ListItem
|
||||||
|
title={Locale.Settings.Access.OpenAI.ApiKey.Title}
|
||||||
|
subTitle={Locale.Settings.Access.OpenAI.ApiKey.SubTitle}
|
||||||
|
>
|
||||||
|
<PasswordInput
|
||||||
|
value={accessStore.openaiApiKey}
|
||||||
|
type="text"
|
||||||
|
placeholder={
|
||||||
|
Locale.Settings.Access.OpenAI.ApiKey.Placeholder
|
||||||
|
}
|
||||||
|
onChange={(e) => {
|
||||||
|
accessStore.update(
|
||||||
|
(access) =>
|
||||||
|
(access.openaiApiKey = e.currentTarget.value),
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<ListItem
|
||||||
|
title={Locale.Settings.Access.Azure.Endpoint.Title}
|
||||||
|
subTitle={
|
||||||
|
Locale.Settings.Access.Azure.Endpoint.SubTitle +
|
||||||
|
Azure.ExampleEndpoint
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={accessStore.azureUrl}
|
||||||
|
placeholder={Azure.ExampleEndpoint}
|
||||||
|
onChange={(e) =>
|
||||||
|
accessStore.update(
|
||||||
|
(access) =>
|
||||||
|
(access.azureUrl = e.currentTarget.value),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
></input>
|
||||||
|
</ListItem>
|
||||||
|
<ListItem
|
||||||
|
title={Locale.Settings.Access.Azure.ApiKey.Title}
|
||||||
|
subTitle={Locale.Settings.Access.Azure.ApiKey.SubTitle}
|
||||||
|
>
|
||||||
|
<PasswordInput
|
||||||
|
value={accessStore.azureApiKey}
|
||||||
|
type="text"
|
||||||
|
placeholder={
|
||||||
|
Locale.Settings.Access.Azure.ApiKey.Placeholder
|
||||||
|
}
|
||||||
|
onChange={(e) => {
|
||||||
|
accessStore.update(
|
||||||
|
(access) =>
|
||||||
|
(access.azureApiKey = e.currentTarget.value),
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
<ListItem
|
||||||
|
title={Locale.Settings.Access.Azure.ApiVerion.Title}
|
||||||
|
subTitle={
|
||||||
|
Locale.Settings.Access.Azure.ApiVerion.SubTitle
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={accessStore.azureApiVersion}
|
||||||
|
placeholder="2023-08-01-preview"
|
||||||
|
onChange={(e) =>
|
||||||
|
accessStore.update(
|
||||||
|
(access) =>
|
||||||
|
(access.azureApiVersion =
|
||||||
|
e.currentTarget.value),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
></input>
|
||||||
|
</ListItem>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!shouldHideBalanceQuery && !clientConfig?.isApp ? (
|
||||||
<ListItem
|
<ListItem
|
||||||
title={Locale.Settings.Usage.Title}
|
title={Locale.Settings.Usage.Title}
|
||||||
subTitle={
|
subTitle={
|
||||||
@@ -943,8 +1089,8 @@ export function Settings() {
|
|||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
<ListItem
|
<ListItem
|
||||||
title={Locale.Settings.CustomModel.Title}
|
title={Locale.Settings.Access.CustomModel.Title}
|
||||||
subTitle={Locale.Settings.CustomModel.SubTitle}
|
subTitle={Locale.Settings.Access.CustomModel.SubTitle}
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useRef, useCallback } from "react";
|
import { useEffect, useRef, useMemo } from "react";
|
||||||
|
|
||||||
import styles from "./home.module.scss";
|
import styles from "./home.module.scss";
|
||||||
|
|
||||||
@@ -8,6 +8,7 @@ import GithubIcon from "../icons/github.svg";
|
|||||||
import ChatGptIcon from "../icons/chatgpt.svg";
|
import ChatGptIcon from "../icons/chatgpt.svg";
|
||||||
import AddIcon from "../icons/add.svg";
|
import AddIcon from "../icons/add.svg";
|
||||||
import CloseIcon from "../icons/close.svg";
|
import CloseIcon from "../icons/close.svg";
|
||||||
|
import DeleteIcon from "../icons/delete.svg";
|
||||||
import MaskIcon from "../icons/mask.svg";
|
import MaskIcon from "../icons/mask.svg";
|
||||||
import PluginIcon from "../icons/plugin.svg";
|
import PluginIcon from "../icons/plugin.svg";
|
||||||
import DragIcon from "../icons/drag.svg";
|
import DragIcon from "../icons/drag.svg";
|
||||||
@@ -26,7 +27,7 @@ import {
|
|||||||
} from "../constant";
|
} from "../constant";
|
||||||
|
|
||||||
import { Link, useNavigate } from "react-router-dom";
|
import { Link, useNavigate } from "react-router-dom";
|
||||||
import { useMobileScreen } from "../utils";
|
import { isIOS, useMobileScreen } from "../utils";
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
import { showConfirm, showToast } from "./ui-lib";
|
import { showConfirm, showToast } from "./ui-lib";
|
||||||
|
|
||||||
@@ -134,6 +135,11 @@ export function SideBar(props: { className?: string }) {
|
|||||||
const { onDragStart, shouldNarrow } = useDragSideBar();
|
const { onDragStart, shouldNarrow } = useDragSideBar();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const config = useAppConfig();
|
const config = useAppConfig();
|
||||||
|
const isMobileScreen = useMobileScreen();
|
||||||
|
const isIOSMobile = useMemo(
|
||||||
|
() => isIOS() && isMobileScreen,
|
||||||
|
[isMobileScreen],
|
||||||
|
);
|
||||||
|
|
||||||
useHotKey();
|
useHotKey();
|
||||||
|
|
||||||
@@ -142,6 +148,10 @@ export function SideBar(props: { className?: string }) {
|
|||||||
className={`${styles.sidebar} ${props.className} ${
|
className={`${styles.sidebar} ${props.className} ${
|
||||||
shouldNarrow && styles["narrow-sidebar"]
|
shouldNarrow && styles["narrow-sidebar"]
|
||||||
}`}
|
}`}
|
||||||
|
style={{
|
||||||
|
// #3016 disable transition on ios mobile screen
|
||||||
|
transition: isMobileScreen && isIOSMobile ? "none" : undefined,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div className={styles["sidebar-header"]} data-tauri-drag-region>
|
<div className={styles["sidebar-header"]} data-tauri-drag-region>
|
||||||
<div className={styles["sidebar-title"]} data-tauri-drag-region>
|
<div className={styles["sidebar-title"]} data-tauri-drag-region>
|
||||||
@@ -160,7 +170,13 @@ export function SideBar(props: { className?: string }) {
|
|||||||
icon={<MaskIcon />}
|
icon={<MaskIcon />}
|
||||||
text={shouldNarrow ? undefined : Locale.Mask.Name}
|
text={shouldNarrow ? undefined : Locale.Mask.Name}
|
||||||
className={styles["sidebar-bar-button"]}
|
className={styles["sidebar-bar-button"]}
|
||||||
onClick={() => navigate(Path.NewChat, { state: { fromHome: true } })}
|
onClick={() => {
|
||||||
|
if (config.dontShowMaskSplashScreen !== true) {
|
||||||
|
navigate(Path.NewChat, { state: { fromHome: true } });
|
||||||
|
} else {
|
||||||
|
navigate(Path.Masks, { state: { fromHome: true } });
|
||||||
|
}
|
||||||
|
}}
|
||||||
shadow
|
shadow
|
||||||
/>
|
/>
|
||||||
<IconButton
|
<IconButton
|
||||||
@@ -187,7 +203,7 @@ export function SideBar(props: { className?: string }) {
|
|||||||
<div className={styles["sidebar-actions"]}>
|
<div className={styles["sidebar-actions"]}>
|
||||||
<div className={styles["sidebar-action"] + " " + styles.mobile}>
|
<div className={styles["sidebar-action"] + " " + styles.mobile}>
|
||||||
<IconButton
|
<IconButton
|
||||||
icon={<CloseIcon />}
|
icon={<DeleteIcon />}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
if (await showConfirm(Locale.Home.DeleteChat)) {
|
if (await showConfirm(Locale.Home.DeleteChat)) {
|
||||||
chatStore.deleteSession(chatStore.currentSessionIndex);
|
chatStore.deleteSession(chatStore.currentSessionIndex);
|
||||||
|
@@ -235,7 +235,7 @@
|
|||||||
.select-with-icon-select {
|
.select-with-icon-select {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
border: var(--border-in-light);
|
border: var(--border-in-light);
|
||||||
padding: 10px 25px 10px 10px;
|
padding: 10px 35px 10px 10px;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
appearance: none;
|
appearance: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
@@ -70,14 +70,12 @@ export function ListItem(props: {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function List(props: {
|
export function List(props: { children: React.ReactNode; id?: string }) {
|
||||||
children:
|
return (
|
||||||
| Array<JSX.Element | null | undefined>
|
<div className={styles.list} id={props.id}>
|
||||||
| JSX.Element
|
{props.children}
|
||||||
| null
|
</div>
|
||||||
| undefined;
|
);
|
||||||
}) {
|
|
||||||
return <div className={styles.list}>{props.children}</div>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Loading() {
|
export function Loading() {
|
||||||
@@ -99,8 +97,9 @@ export function Loading() {
|
|||||||
interface ModalProps {
|
interface ModalProps {
|
||||||
title: string;
|
title: string;
|
||||||
children?: any;
|
children?: any;
|
||||||
actions?: JSX.Element[];
|
actions?: React.ReactNode[];
|
||||||
defaultMax?: boolean;
|
defaultMax?: boolean;
|
||||||
|
footer?: React.ReactNode;
|
||||||
onClose?: () => void;
|
onClose?: () => void;
|
||||||
}
|
}
|
||||||
export function Modal(props: ModalProps) {
|
export function Modal(props: ModalProps) {
|
||||||
@@ -149,6 +148,7 @@ export function Modal(props: ModalProps) {
|
|||||||
<div className={styles["modal-content"]}>{props.children}</div>
|
<div className={styles["modal-content"]}>{props.children}</div>
|
||||||
|
|
||||||
<div className={styles["modal-footer"]}>
|
<div className={styles["modal-footer"]}>
|
||||||
|
{props.footer}
|
||||||
<div className={styles["modal-actions"]}>
|
<div className={styles["modal-actions"]}>
|
||||||
{props.actions?.map((action, i) => (
|
{props.actions?.map((action, i) => (
|
||||||
<div key={i} className={styles["modal-action"]}>
|
<div key={i} className={styles["modal-action"]}>
|
||||||
|
@@ -1,18 +1,31 @@
|
|||||||
import md5 from "spark-md5";
|
import md5 from "spark-md5";
|
||||||
|
import { DEFAULT_MODELS } from "../constant";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
namespace NodeJS {
|
namespace NodeJS {
|
||||||
interface ProcessEnv {
|
interface ProcessEnv {
|
||||||
|
PROXY_URL?: string; // docker only
|
||||||
|
|
||||||
OPENAI_API_KEY?: string;
|
OPENAI_API_KEY?: string;
|
||||||
CODE?: string;
|
CODE?: string;
|
||||||
|
|
||||||
BASE_URL?: string;
|
BASE_URL?: string;
|
||||||
PROXY_URL?: string;
|
OPENAI_ORG_ID?: string; // openai only
|
||||||
|
|
||||||
VERCEL?: string;
|
VERCEL?: string;
|
||||||
HIDE_USER_API_KEY?: string; // disable user's api key input
|
|
||||||
DISABLE_GPT4?: string; // allow user to use gpt-4 or not
|
|
||||||
BUILD_MODE?: "standalone" | "export";
|
BUILD_MODE?: "standalone" | "export";
|
||||||
BUILD_APP?: string; // is building desktop app
|
BUILD_APP?: string; // is building desktop app
|
||||||
HIDE_BALANCE_QUERY?: string; // allow user to query balance or not
|
|
||||||
|
HIDE_USER_API_KEY?: string; // disable user's api key input
|
||||||
|
DISABLE_GPT4?: string; // allow user to use gpt-4 or not
|
||||||
|
ENABLE_BALANCE_QUERY?: string; // allow user to query balance or not
|
||||||
|
DISABLE_FAST_LINK?: string; // disallow parse settings from url or not
|
||||||
|
CUSTOM_MODELS?: string; // to control custom models
|
||||||
|
|
||||||
|
// azure only
|
||||||
|
AZURE_URL?: string; // https://{azure-url}/openai/deployments/{deploy-name}
|
||||||
|
AZURE_API_KEY?: string;
|
||||||
|
AZURE_API_VERSION?: string;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -37,16 +50,47 @@ export const getServerSideConfig = () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const disableGPT4 = !!process.env.DISABLE_GPT4;
|
||||||
|
let customModels = process.env.CUSTOM_MODELS ?? "";
|
||||||
|
|
||||||
|
if (disableGPT4) {
|
||||||
|
if (customModels) customModels += ",";
|
||||||
|
customModels += DEFAULT_MODELS.filter((m) => m.name.startsWith("gpt-4"))
|
||||||
|
.map((m) => "-" + m.name)
|
||||||
|
.join(",");
|
||||||
|
}
|
||||||
|
|
||||||
|
const isAzure = !!process.env.AZURE_URL;
|
||||||
|
|
||||||
|
const apiKeyEnvVar = process.env.OPENAI_API_KEY ?? "";
|
||||||
|
const apiKeys = apiKeyEnvVar.split(",").map((v) => v.trim());
|
||||||
|
const randomIndex = Math.floor(Math.random() * apiKeys.length);
|
||||||
|
const apiKey = apiKeys[randomIndex];
|
||||||
|
console.log(
|
||||||
|
`[Server Config] using ${randomIndex + 1} of ${apiKeys.length} api key`,
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
apiKey: process.env.OPENAI_API_KEY,
|
baseUrl: process.env.BASE_URL,
|
||||||
|
apiKey,
|
||||||
|
openaiOrgId: process.env.OPENAI_ORG_ID,
|
||||||
|
|
||||||
|
isAzure,
|
||||||
|
azureUrl: process.env.AZURE_URL,
|
||||||
|
azureApiKey: process.env.AZURE_API_KEY,
|
||||||
|
azureApiVersion: process.env.AZURE_API_VERSION,
|
||||||
|
|
||||||
|
needCode: ACCESS_CODES.size > 0,
|
||||||
code: process.env.CODE,
|
code: process.env.CODE,
|
||||||
codes: ACCESS_CODES,
|
codes: ACCESS_CODES,
|
||||||
needCode: ACCESS_CODES.size > 0,
|
|
||||||
baseUrl: process.env.BASE_URL,
|
|
||||||
proxyUrl: process.env.PROXY_URL,
|
proxyUrl: process.env.PROXY_URL,
|
||||||
isVercel: !!process.env.VERCEL,
|
isVercel: !!process.env.VERCEL,
|
||||||
|
|
||||||
hideUserApiKey: !!process.env.HIDE_USER_API_KEY,
|
hideUserApiKey: !!process.env.HIDE_USER_API_KEY,
|
||||||
disableGPT4: !!process.env.DISABLE_GPT4,
|
disableGPT4,
|
||||||
hideBalanceQuery: !!process.env.HIDE_BALANCE_QUERY,
|
hideBalanceQuery: !process.env.ENABLE_BALANCE_QUERY,
|
||||||
|
disableFastLink: !!process.env.DISABLE_FAST_LINK,
|
||||||
|
customModels,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@@ -8,8 +8,9 @@ export const FETCH_COMMIT_URL = `https://api.github.com/repos/${OWNER}/${REPO}/c
|
|||||||
export const FETCH_TAG_URL = `https://api.github.com/repos/${OWNER}/${REPO}/tags?per_page=1`;
|
export const FETCH_TAG_URL = `https://api.github.com/repos/${OWNER}/${REPO}/tags?per_page=1`;
|
||||||
export const RUNTIME_CONFIG_DOM = "danger-runtime-config";
|
export const RUNTIME_CONFIG_DOM = "danger-runtime-config";
|
||||||
|
|
||||||
export const DEFAULT_CORS_HOST = "https://nb.nextweb.fun";
|
export const DEFAULT_CORS_HOST = "https://a.nextweb.fun";
|
||||||
export const DEFAULT_API_HOST = `${DEFAULT_CORS_HOST}/api/proxy`;
|
export const DEFAULT_API_HOST = `${DEFAULT_CORS_HOST}/api/proxy`;
|
||||||
|
export const OPENAI_BASE_URL = "https://api.openai.com";
|
||||||
|
|
||||||
export enum Path {
|
export enum Path {
|
||||||
Home = "/",
|
Home = "/",
|
||||||
@@ -22,10 +23,12 @@ export enum Path {
|
|||||||
|
|
||||||
export enum ApiPath {
|
export enum ApiPath {
|
||||||
Cors = "/api/cors",
|
Cors = "/api/cors",
|
||||||
|
OpenAI = "/api/openai",
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum SlotID {
|
export enum SlotID {
|
||||||
AppBody = "app-body",
|
AppBody = "app-body",
|
||||||
|
CustomModel = "custom-model",
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum FileName {
|
export enum FileName {
|
||||||
@@ -59,6 +62,11 @@ export const REQUEST_TIMEOUT_MS = 60000;
|
|||||||
|
|
||||||
export const EXPORT_MESSAGE_CLASS_NAME = "export-markdown";
|
export const EXPORT_MESSAGE_CLASS_NAME = "export-markdown";
|
||||||
|
|
||||||
|
export enum ServiceProvider {
|
||||||
|
OpenAI = "OpenAI",
|
||||||
|
Azure = "Azure",
|
||||||
|
}
|
||||||
|
|
||||||
export const OpenaiPath = {
|
export const OpenaiPath = {
|
||||||
ChatPath: "v1/chat/completions",
|
ChatPath: "v1/chat/completions",
|
||||||
UsagePath: "dashboard/billing/usage",
|
UsagePath: "dashboard/billing/usage",
|
||||||
@@ -66,15 +74,28 @@ export const OpenaiPath = {
|
|||||||
ListModelPath: "v1/models",
|
ListModelPath: "v1/models",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const Azure = {
|
||||||
|
ExampleEndpoint: "https://{resource-url}/openai/deployments/{deploy-id}",
|
||||||
|
};
|
||||||
|
|
||||||
export const DEFAULT_INPUT_TEMPLATE = `{{input}}`; // input / time / model / lang
|
export const DEFAULT_INPUT_TEMPLATE = `{{input}}`; // input / time / model / lang
|
||||||
export const DEFAULT_SYSTEM_TEMPLATE = `
|
export const DEFAULT_SYSTEM_TEMPLATE = `
|
||||||
You are ChatGPT, a large language model trained by OpenAI.
|
You are ChatGPT, a large language model trained by OpenAI.
|
||||||
Knowledge cutoff: 2021-09
|
Knowledge cutoff: {{cutoff}}
|
||||||
Current model: {{model}}
|
Current model: {{model}}
|
||||||
Current time: {{time}}`;
|
Current time: {{time}}
|
||||||
|
Latex inline: $x^2$
|
||||||
|
Latex block: $$e=mc^2$$
|
||||||
|
`;
|
||||||
|
|
||||||
export const SUMMARIZE_MODEL = "gpt-3.5-turbo";
|
export const SUMMARIZE_MODEL = "gpt-3.5-turbo";
|
||||||
|
|
||||||
|
export const KnowledgeCutOffDate: Record<string, string> = {
|
||||||
|
default: "2021-09",
|
||||||
|
"gpt-4-1106-preview": "2023-04",
|
||||||
|
"gpt-4-vision-preview": "2023-04",
|
||||||
|
};
|
||||||
|
|
||||||
export const DEFAULT_MODELS = [
|
export const DEFAULT_MODELS = [
|
||||||
{
|
{
|
||||||
name: "gpt-4",
|
name: "gpt-4",
|
||||||
@@ -100,6 +121,14 @@ export const DEFAULT_MODELS = [
|
|||||||
name: "gpt-4-32k-0613",
|
name: "gpt-4-32k-0613",
|
||||||
available: true,
|
available: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "gpt-4-1106-preview",
|
||||||
|
available: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "gpt-4-vision-preview",
|
||||||
|
available: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "gpt-3.5-turbo",
|
name: "gpt-3.5-turbo",
|
||||||
available: true,
|
available: true,
|
||||||
@@ -112,6 +141,10 @@ export const DEFAULT_MODELS = [
|
|||||||
name: "gpt-3.5-turbo-0613",
|
name: "gpt-3.5-turbo-0613",
|
||||||
available: true,
|
available: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "gpt-3.5-turbo-1106",
|
||||||
|
available: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "gpt-3.5-turbo-16k",
|
name: "gpt-3.5-turbo-16k",
|
||||||
available: true,
|
available: true,
|
||||||
|
12
app/global.d.ts
vendored
@@ -13,5 +13,17 @@ declare module "*.svg";
|
|||||||
declare interface Window {
|
declare interface Window {
|
||||||
__TAURI__?: {
|
__TAURI__?: {
|
||||||
writeText(text: string): Promise<void>;
|
writeText(text: string): Promise<void>;
|
||||||
|
invoke(command: string, payload?: Record<string, unknown>): Promise<any>;
|
||||||
|
dialog: {
|
||||||
|
save(options?: Record<string, unknown>): Promise<string | null>;
|
||||||
|
};
|
||||||
|
fs: {
|
||||||
|
writeBinaryFile(path: string, data: Uint8Array): Promise<void>;
|
||||||
|
};
|
||||||
|
notification:{
|
||||||
|
requestPermission(): Promise<Permission>;
|
||||||
|
isPermissionGranted(): Promise<boolean>;
|
||||||
|
sendNotification(options: string | Options): void;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@@ -10,6 +10,7 @@ const ar: PartialLocaleType = {
|
|||||||
Auth: {
|
Auth: {
|
||||||
Title: "تحتاج إلى رمز الوصول",
|
Title: "تحتاج إلى رمز الوصول",
|
||||||
Tips: "يرجى إدخال رمز الوصول أدناه",
|
Tips: "يرجى إدخال رمز الوصول أدناه",
|
||||||
|
SubTips: "أو أدخل مفتاح واجهة برمجة تطبيقات OpenAI الخاص بك",
|
||||||
Input: "رمز الوصول",
|
Input: "رمز الوصول",
|
||||||
Confirm: "تأكيد",
|
Confirm: "تأكيد",
|
||||||
Later: "لاحقًا",
|
Later: "لاحقًا",
|
||||||
@@ -166,11 +167,7 @@ ${builtin} مدمجة، ${custom} تم تعريفها من قبل المستخد
|
|||||||
Title: "حد الضغط للتاريخ",
|
Title: "حد الضغط للتاريخ",
|
||||||
SubTitle: "سيتم الضغط إذا تجاوزت طول الرسائل غير المضغوطة الحد المحدد",
|
SubTitle: "سيتم الضغط إذا تجاوزت طول الرسائل غير المضغوطة الحد المحدد",
|
||||||
},
|
},
|
||||||
Token: {
|
|
||||||
Title: "مفتاح API",
|
|
||||||
SubTitle: "استخدم مفتاحك لتجاوز حد رمز الوصول",
|
|
||||||
Placeholder: "مفتاح OpenAI API",
|
|
||||||
},
|
|
||||||
Usage: {
|
Usage: {
|
||||||
Title: "رصيد الحساب",
|
Title: "رصيد الحساب",
|
||||||
SubTitle(used: any, total: any) {
|
SubTitle(used: any, total: any) {
|
||||||
@@ -180,15 +177,7 @@ ${builtin} مدمجة، ${custom} تم تعريفها من قبل المستخد
|
|||||||
Check: "التحقق",
|
Check: "التحقق",
|
||||||
NoAccess: "أدخل مفتاح API للتحقق من الرصيد",
|
NoAccess: "أدخل مفتاح API للتحقق من الرصيد",
|
||||||
},
|
},
|
||||||
AccessCode: {
|
|
||||||
Title: "رمز الوصول",
|
|
||||||
SubTitle: "تم تمكين التحكم في الوصول",
|
|
||||||
Placeholder: "رمز الوصول المطلوب",
|
|
||||||
},
|
|
||||||
Endpoint: {
|
|
||||||
Title: "نقطة النهاية",
|
|
||||||
SubTitle: "يجب أن تبدأ نقطة النهاية المخصصة بـ http(s)://",
|
|
||||||
},
|
|
||||||
Model: "النموذج",
|
Model: "النموذج",
|
||||||
Temperature: {
|
Temperature: {
|
||||||
Title: "الحرارة",
|
Title: "الحرارة",
|
||||||
|
@@ -10,6 +10,7 @@ const bn: PartialLocaleType = {
|
|||||||
Auth: {
|
Auth: {
|
||||||
Title: "একটি অ্যাক্সেস কোড প্রয়োজন",
|
Title: "একটি অ্যাক্সেস কোড প্রয়োজন",
|
||||||
Tips: "নীচে অ্যাক্সেস কোড ইনপুট করুন",
|
Tips: "নীচে অ্যাক্সেস কোড ইনপুট করুন",
|
||||||
|
SubTips: "অথবা আপনার OpenAI API কী প্রবেশ করুন",
|
||||||
Input: "অ্যাক্সেস কোড",
|
Input: "অ্যাক্সেস কোড",
|
||||||
Confirm: "নিশ্চিত করুন",
|
Confirm: "নিশ্চিত করুন",
|
||||||
Later: "পরে",
|
Later: "পরে",
|
||||||
@@ -198,11 +199,7 @@ const bn: PartialLocaleType = {
|
|||||||
SubTitle:
|
SubTitle:
|
||||||
"নকুল বার্তা দৈর্ঘ্য সীমা অতিক্রান্ত হলে ঐ বার্তাটি সঙ্কুচিত হবে",
|
"নকুল বার্তা দৈর্ঘ্য সীমা অতিক্রান্ত হলে ঐ বার্তাটি সঙ্কুচিত হবে",
|
||||||
},
|
},
|
||||||
Token: {
|
|
||||||
Title: "অ্যাপি কী",
|
|
||||||
SubTitle: "অ্যাক্সেস কোড সীমা উপেক্ষা করতে আপনার কীটি ব্যবহার করুন",
|
|
||||||
Placeholder: "OpenAI API কী",
|
|
||||||
},
|
|
||||||
Usage: {
|
Usage: {
|
||||||
Title: "একাউন্ট ব্যালেন্স",
|
Title: "একাউন্ট ব্যালেন্স",
|
||||||
SubTitle(used: any, total: any) {
|
SubTitle(used: any, total: any) {
|
||||||
@@ -212,15 +209,7 @@ const bn: PartialLocaleType = {
|
|||||||
Check: "চেক",
|
Check: "চেক",
|
||||||
NoAccess: "ব্যালেন্স চেক করতে অ্যাপি কী ইনপুট করুন",
|
NoAccess: "ব্যালেন্স চেক করতে অ্যাপি কী ইনপুট করুন",
|
||||||
},
|
},
|
||||||
AccessCode: {
|
|
||||||
Title: "অ্যাক্সেস কোড",
|
|
||||||
SubTitle: "অ্যাক্সেস নিয়ন্ত্রণ সক্রিয়",
|
|
||||||
Placeholder: "অ্যাক্সেস কোড প্রয়োজন",
|
|
||||||
},
|
|
||||||
Endpoint: {
|
|
||||||
Title: "ইনটারপয়েন্ট",
|
|
||||||
SubTitle: "কাস্টম এন্ডপয়েন্টটি হতে হবে http(s):// দিয়ে শুরু হতে হবে",
|
|
||||||
},
|
|
||||||
Model: "মডেল",
|
Model: "মডেল",
|
||||||
Temperature: {
|
Temperature: {
|
||||||
Title: "তাপমাত্রা",
|
Title: "তাপমাত্রা",
|
||||||
|
@@ -13,6 +13,7 @@ const cn = {
|
|||||||
Auth: {
|
Auth: {
|
||||||
Title: "需要密码",
|
Title: "需要密码",
|
||||||
Tips: "管理员开启了密码验证,请在下方填入访问码",
|
Tips: "管理员开启了密码验证,请在下方填入访问码",
|
||||||
|
SubTips: "或者输入你的 OpenAI API 密钥",
|
||||||
Input: "在此处填写访问码",
|
Input: "在此处填写访问码",
|
||||||
Confirm: "确认",
|
Confirm: "确认",
|
||||||
Later: "稍后再说",
|
Later: "稍后再说",
|
||||||
@@ -84,8 +85,8 @@ const cn = {
|
|||||||
Copy: "全部复制",
|
Copy: "全部复制",
|
||||||
Download: "下载文件",
|
Download: "下载文件",
|
||||||
Share: "分享到 ShareGPT",
|
Share: "分享到 ShareGPT",
|
||||||
MessageFromYou: "来自你的消息",
|
MessageFromYou: "用户",
|
||||||
MessageFromChatGPT: "来自 ChatGPT 的消息",
|
MessageFromChatGPT: "ChatGPT",
|
||||||
Format: {
|
Format: {
|
||||||
Title: "导出格式",
|
Title: "导出格式",
|
||||||
SubTitle: "可以导出 Markdown 文本或者 PNG 图片",
|
SubTitle: "可以导出 Markdown 文本或者 PNG 图片",
|
||||||
@@ -257,11 +258,6 @@ const cn = {
|
|||||||
Title: "历史消息长度压缩阈值",
|
Title: "历史消息长度压缩阈值",
|
||||||
SubTitle: "当未压缩的历史消息超过该值时,将进行压缩",
|
SubTitle: "当未压缩的历史消息超过该值时,将进行压缩",
|
||||||
},
|
},
|
||||||
Token: {
|
|
||||||
Title: "API Key",
|
|
||||||
SubTitle: "使用自己的 Key 可绕过密码访问限制",
|
|
||||||
Placeholder: "OpenAI API Key",
|
|
||||||
},
|
|
||||||
|
|
||||||
Usage: {
|
Usage: {
|
||||||
Title: "余额查询",
|
Title: "余额查询",
|
||||||
@@ -272,19 +268,56 @@ const cn = {
|
|||||||
Check: "重新检查",
|
Check: "重新检查",
|
||||||
NoAccess: "输入 API Key 或访问密码查看余额",
|
NoAccess: "输入 API Key 或访问密码查看余额",
|
||||||
},
|
},
|
||||||
AccessCode: {
|
|
||||||
Title: "访问密码",
|
Access: {
|
||||||
SubTitle: "管理员已开启加密访问",
|
AccessCode: {
|
||||||
Placeholder: "请输入访问密码",
|
Title: "访问密码",
|
||||||
},
|
SubTitle: "管理员已开启加密访问",
|
||||||
Endpoint: {
|
Placeholder: "请输入访问密码",
|
||||||
Title: "接口地址",
|
},
|
||||||
SubTitle: "除默认地址外,必须包含 http(s)://",
|
CustomEndpoint: {
|
||||||
},
|
Title: "自定义接口",
|
||||||
CustomModel: {
|
SubTitle: "是否使用自定义 Azure 或 OpenAI 服务",
|
||||||
Title: "自定义模型名",
|
},
|
||||||
SubTitle: "增加自定义模型可选项,使用英文逗号隔开",
|
Provider: {
|
||||||
|
Title: "模型服务商",
|
||||||
|
SubTitle: "切换不同的服务商",
|
||||||
|
},
|
||||||
|
OpenAI: {
|
||||||
|
ApiKey: {
|
||||||
|
Title: "API Key",
|
||||||
|
SubTitle: "使用自定义 OpenAI Key 绕过密码访问限制",
|
||||||
|
Placeholder: "OpenAI API Key",
|
||||||
|
},
|
||||||
|
|
||||||
|
Endpoint: {
|
||||||
|
Title: "接口地址",
|
||||||
|
SubTitle: "除默认地址外,必须包含 http(s)://",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Azure: {
|
||||||
|
ApiKey: {
|
||||||
|
Title: "接口密钥",
|
||||||
|
SubTitle: "使用自定义 Azure Key 绕过密码访问限制",
|
||||||
|
Placeholder: "Azure API Key",
|
||||||
|
},
|
||||||
|
|
||||||
|
Endpoint: {
|
||||||
|
Title: "接口地址",
|
||||||
|
SubTitle: "样例:",
|
||||||
|
},
|
||||||
|
|
||||||
|
ApiVerion: {
|
||||||
|
Title: "接口版本 (azure api version)",
|
||||||
|
SubTitle: "选择指定的部分版本",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
CustomModel: {
|
||||||
|
Title: "自定义模型名",
|
||||||
|
SubTitle: "增加自定义模型可选项,使用英文逗号隔开",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
Model: "模型 (model)",
|
Model: "模型 (model)",
|
||||||
Temperature: {
|
Temperature: {
|
||||||
Title: "随机性 (temperature)",
|
Title: "随机性 (temperature)",
|
||||||
@@ -323,6 +356,10 @@ const cn = {
|
|||||||
Success: "已写入剪切板",
|
Success: "已写入剪切板",
|
||||||
Failed: "复制失败,请赋予剪切板权限",
|
Failed: "复制失败,请赋予剪切板权限",
|
||||||
},
|
},
|
||||||
|
Download: {
|
||||||
|
Success: "内容已下载到您的目录。",
|
||||||
|
Failed: "下载失败。",
|
||||||
|
},
|
||||||
Context: {
|
Context: {
|
||||||
Toast: (x: any) => `包含 ${x} 条预设提示词`,
|
Toast: (x: any) => `包含 ${x} 条预设提示词`,
|
||||||
Edit: "当前对话设置",
|
Edit: "当前对话设置",
|
||||||
@@ -404,6 +441,9 @@ const cn = {
|
|||||||
Config: "配置",
|
Config: "配置",
|
||||||
},
|
},
|
||||||
Exporter: {
|
Exporter: {
|
||||||
|
Description : {
|
||||||
|
Title: "只有清除上下文之后的消息会被展示"
|
||||||
|
},
|
||||||
Model: "模型",
|
Model: "模型",
|
||||||
Messages: "消息",
|
Messages: "消息",
|
||||||
Topic: "主题",
|
Topic: "主题",
|
||||||
|
@@ -124,11 +124,7 @@ const cs: PartialLocaleType = {
|
|||||||
SubTitle:
|
SubTitle:
|
||||||
"Komprese proběhne, pokud délka nekomprimovaných zpráv přesáhne tuto hodnotu",
|
"Komprese proběhne, pokud délka nekomprimovaných zpráv přesáhne tuto hodnotu",
|
||||||
},
|
},
|
||||||
Token: {
|
|
||||||
Title: "API klíč",
|
|
||||||
SubTitle: "Použitím klíče ignorujete omezení přístupového kódu",
|
|
||||||
Placeholder: "Klíč API OpenAI",
|
|
||||||
},
|
|
||||||
Usage: {
|
Usage: {
|
||||||
Title: "Stav účtu",
|
Title: "Stav účtu",
|
||||||
SubTitle(used: any, total: any) {
|
SubTitle(used: any, total: any) {
|
||||||
@@ -138,11 +134,7 @@ const cs: PartialLocaleType = {
|
|||||||
Check: "Zkontrolovat",
|
Check: "Zkontrolovat",
|
||||||
NoAccess: "Pro kontrolu zůstatku zadejte klíč API",
|
NoAccess: "Pro kontrolu zůstatku zadejte klíč API",
|
||||||
},
|
},
|
||||||
AccessCode: {
|
|
||||||
Title: "Přístupový kód",
|
|
||||||
SubTitle: "Kontrola přístupu povolena",
|
|
||||||
Placeholder: "Potřebujete přístupový kód",
|
|
||||||
},
|
|
||||||
Model: "Model",
|
Model: "Model",
|
||||||
Temperature: {
|
Temperature: {
|
||||||
Title: "Teplota",
|
Title: "Teplota",
|
||||||
|
@@ -124,12 +124,7 @@ const de: PartialLocaleType = {
|
|||||||
SubTitle:
|
SubTitle:
|
||||||
"Komprimierung, wenn die Länge der unkomprimierten Nachrichten den Wert überschreitet",
|
"Komprimierung, wenn die Länge der unkomprimierten Nachrichten den Wert überschreitet",
|
||||||
},
|
},
|
||||||
Token: {
|
|
||||||
Title: "API-Schlüssel",
|
|
||||||
SubTitle:
|
|
||||||
"Verwenden Sie Ihren Schlüssel, um das Zugangscode-Limit zu ignorieren",
|
|
||||||
Placeholder: "OpenAI API-Schlüssel",
|
|
||||||
},
|
|
||||||
Usage: {
|
Usage: {
|
||||||
Title: "Kontostand",
|
Title: "Kontostand",
|
||||||
SubTitle(used: any, total: any) {
|
SubTitle(used: any, total: any) {
|
||||||
@@ -139,11 +134,6 @@ const de: PartialLocaleType = {
|
|||||||
Check: "Erneut prüfen",
|
Check: "Erneut prüfen",
|
||||||
NoAccess: "API-Schlüssel eingeben, um den Kontostand zu überprüfen",
|
NoAccess: "API-Schlüssel eingeben, um den Kontostand zu überprüfen",
|
||||||
},
|
},
|
||||||
AccessCode: {
|
|
||||||
Title: "Zugangscode",
|
|
||||||
SubTitle: "Zugangskontrolle aktiviert",
|
|
||||||
Placeholder: "Zugangscode erforderlich",
|
|
||||||
},
|
|
||||||
Model: "Modell",
|
Model: "Modell",
|
||||||
Temperature: {
|
Temperature: {
|
||||||
Title: "Temperature", //Temperatur
|
Title: "Temperature", //Temperatur
|
||||||
|
@@ -15,6 +15,7 @@ const en: LocaleType = {
|
|||||||
Auth: {
|
Auth: {
|
||||||
Title: "Need Access Code",
|
Title: "Need Access Code",
|
||||||
Tips: "Please enter access code below",
|
Tips: "Please enter access code below",
|
||||||
|
SubTips: "Or enter your OpenAI API Key",
|
||||||
Input: "access code",
|
Input: "access code",
|
||||||
Confirm: "Confirm",
|
Confirm: "Confirm",
|
||||||
Later: "Later",
|
Later: "Later",
|
||||||
@@ -261,11 +262,7 @@ const en: LocaleType = {
|
|||||||
SubTitle:
|
SubTitle:
|
||||||
"Will compress if uncompressed messages length exceeds the value",
|
"Will compress if uncompressed messages length exceeds the value",
|
||||||
},
|
},
|
||||||
Token: {
|
|
||||||
Title: "API Key",
|
|
||||||
SubTitle: "Use your key to ignore access code limit",
|
|
||||||
Placeholder: "OpenAI API Key",
|
|
||||||
},
|
|
||||||
Usage: {
|
Usage: {
|
||||||
Title: "Account Balance",
|
Title: "Account Balance",
|
||||||
SubTitle(used: any, total: any) {
|
SubTitle(used: any, total: any) {
|
||||||
@@ -275,19 +272,55 @@ const en: LocaleType = {
|
|||||||
Check: "Check",
|
Check: "Check",
|
||||||
NoAccess: "Enter API Key to check balance",
|
NoAccess: "Enter API Key to check balance",
|
||||||
},
|
},
|
||||||
AccessCode: {
|
Access: {
|
||||||
Title: "Access Code",
|
AccessCode: {
|
||||||
SubTitle: "Access control enabled",
|
Title: "Access Code",
|
||||||
Placeholder: "Need Access Code",
|
SubTitle: "Access control Enabled",
|
||||||
},
|
Placeholder: "Enter Code",
|
||||||
Endpoint: {
|
},
|
||||||
Title: "Endpoint",
|
CustomEndpoint: {
|
||||||
SubTitle: "Custom endpoint must start with http(s)://",
|
Title: "Custom Endpoint",
|
||||||
},
|
SubTitle: "Use custom Azure or OpenAI service",
|
||||||
CustomModel: {
|
},
|
||||||
Title: "Custom Models",
|
Provider: {
|
||||||
SubTitle: "Add extra model options, separate by comma",
|
Title: "Model Provider",
|
||||||
|
SubTitle: "Select Azure or OpenAI",
|
||||||
|
},
|
||||||
|
OpenAI: {
|
||||||
|
ApiKey: {
|
||||||
|
Title: "OpenAI API Key",
|
||||||
|
SubTitle: "User custom OpenAI Api Key",
|
||||||
|
Placeholder: "sk-xxx",
|
||||||
|
},
|
||||||
|
|
||||||
|
Endpoint: {
|
||||||
|
Title: "OpenAI Endpoint",
|
||||||
|
SubTitle: "Must starts with http(s):// or use /api/openai as default",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Azure: {
|
||||||
|
ApiKey: {
|
||||||
|
Title: "Azure Api Key",
|
||||||
|
SubTitle: "Check your api key from Azure console",
|
||||||
|
Placeholder: "Azure Api Key",
|
||||||
|
},
|
||||||
|
|
||||||
|
Endpoint: {
|
||||||
|
Title: "Azure Endpoint",
|
||||||
|
SubTitle: "Example: ",
|
||||||
|
},
|
||||||
|
|
||||||
|
ApiVerion: {
|
||||||
|
Title: "Azure Api Version",
|
||||||
|
SubTitle: "Check your api version from azure console",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
CustomModel: {
|
||||||
|
Title: "Custom Models",
|
||||||
|
SubTitle: "Custom model options, seperated by comma",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
Model: "Model",
|
Model: "Model",
|
||||||
Temperature: {
|
Temperature: {
|
||||||
Title: "Temperature",
|
Title: "Temperature",
|
||||||
@@ -329,6 +362,10 @@ const en: LocaleType = {
|
|||||||
Success: "Copied to clipboard",
|
Success: "Copied to clipboard",
|
||||||
Failed: "Copy failed, please grant permission to access clipboard",
|
Failed: "Copy failed, please grant permission to access clipboard",
|
||||||
},
|
},
|
||||||
|
Download: {
|
||||||
|
Success: "Content downloaded to your directory.",
|
||||||
|
Failed: "Download failed.",
|
||||||
|
},
|
||||||
Context: {
|
Context: {
|
||||||
Toast: (x: any) => `With ${x} contextual prompts`,
|
Toast: (x: any) => `With ${x} contextual prompts`,
|
||||||
Edit: "Current Chat Settings",
|
Edit: "Current Chat Settings",
|
||||||
@@ -405,6 +442,9 @@ const en: LocaleType = {
|
|||||||
Config: "Config",
|
Config: "Config",
|
||||||
},
|
},
|
||||||
Exporter: {
|
Exporter: {
|
||||||
|
Description: {
|
||||||
|
Title: "Only messages after clearing the context will be displayed"
|
||||||
|
},
|
||||||
Model: "Model",
|
Model: "Model",
|
||||||
Messages: "Messages",
|
Messages: "Messages",
|
||||||
Topic: "Topic",
|
Topic: "Topic",
|
||||||
|
@@ -124,11 +124,7 @@ const es: PartialLocaleType = {
|
|||||||
SubTitle:
|
SubTitle:
|
||||||
"Se comprimirán los mensajes si la longitud de los mensajes no comprimidos supera el valor",
|
"Se comprimirán los mensajes si la longitud de los mensajes no comprimidos supera el valor",
|
||||||
},
|
},
|
||||||
Token: {
|
|
||||||
Title: "Clave de API",
|
|
||||||
SubTitle: "Utiliza tu clave para ignorar el límite de código de acceso",
|
|
||||||
Placeholder: "Clave de la API de OpenAI",
|
|
||||||
},
|
|
||||||
Usage: {
|
Usage: {
|
||||||
Title: "Saldo de la cuenta",
|
Title: "Saldo de la cuenta",
|
||||||
SubTitle(used: any, total: any) {
|
SubTitle(used: any, total: any) {
|
||||||
@@ -138,11 +134,7 @@ const es: PartialLocaleType = {
|
|||||||
Check: "Comprobar de nuevo",
|
Check: "Comprobar de nuevo",
|
||||||
NoAccess: "Introduzca la clave API para comprobar el saldo",
|
NoAccess: "Introduzca la clave API para comprobar el saldo",
|
||||||
},
|
},
|
||||||
AccessCode: {
|
|
||||||
Title: "Código de acceso",
|
|
||||||
SubTitle: "Control de acceso habilitado",
|
|
||||||
Placeholder: "Necesita código de acceso",
|
|
||||||
},
|
|
||||||
Model: "Modelo",
|
Model: "Modelo",
|
||||||
Temperature: {
|
Temperature: {
|
||||||
Title: "Temperatura",
|
Title: "Temperatura",
|
||||||
|
@@ -173,11 +173,7 @@ const fr: PartialLocaleType = {
|
|||||||
SubTitle:
|
SubTitle:
|
||||||
"Comprimera si la longueur des messages non compressés dépasse cette valeur",
|
"Comprimera si la longueur des messages non compressés dépasse cette valeur",
|
||||||
},
|
},
|
||||||
Token: {
|
|
||||||
Title: "Clé API",
|
|
||||||
SubTitle: "Utilisez votre clé pour ignorer la limite du code d'accès",
|
|
||||||
Placeholder: "Clé OpenAI API",
|
|
||||||
},
|
|
||||||
Usage: {
|
Usage: {
|
||||||
Title: "Solde du compte",
|
Title: "Solde du compte",
|
||||||
SubTitle(used: any, total: any) {
|
SubTitle(used: any, total: any) {
|
||||||
@@ -187,11 +183,7 @@ const fr: PartialLocaleType = {
|
|||||||
Check: "Vérifier",
|
Check: "Vérifier",
|
||||||
NoAccess: "Entrez la clé API pour vérifier le solde",
|
NoAccess: "Entrez la clé API pour vérifier le solde",
|
||||||
},
|
},
|
||||||
AccessCode: {
|
|
||||||
Title: "Code d'accès",
|
|
||||||
SubTitle: "Contrôle d'accès activé",
|
|
||||||
Placeholder: "Code d'accès requis",
|
|
||||||
},
|
|
||||||
Model: "Modèle",
|
Model: "Modèle",
|
||||||
Temperature: {
|
Temperature: {
|
||||||
Title: "Température",
|
Title: "Température",
|
||||||
|
@@ -5,11 +5,12 @@ const id: PartialLocaleType = {
|
|||||||
WIP: "Coming Soon...",
|
WIP: "Coming Soon...",
|
||||||
Error: {
|
Error: {
|
||||||
Unauthorized:
|
Unauthorized:
|
||||||
"Akses tidak diizinkan. Silakan [otorisasi](/#/auth) dengan memasukkan kode akses.",
|
"Akses tidak diizinkan, silakan masukkan kode akses atau masukkan kunci API OpenAI Anda. di halaman [autentikasi](/#/auth) atau di halaman [Pengaturan](/#/settings).",
|
||||||
},
|
},
|
||||||
Auth: {
|
Auth: {
|
||||||
Title: "Diperlukan Kode Akses",
|
Title: "Diperlukan Kode Akses",
|
||||||
Tips: "Masukkan kode akses di bawah",
|
Tips: "Masukkan kode akses di bawah",
|
||||||
|
SubTips: "Atau masukkan kunci API OpenAI Anda",
|
||||||
Input: "Kode Akses",
|
Input: "Kode Akses",
|
||||||
Confirm: "Konfirmasi",
|
Confirm: "Konfirmasi",
|
||||||
Later: "Nanti",
|
Later: "Nanti",
|
||||||
@@ -237,11 +238,7 @@ const id: PartialLocaleType = {
|
|||||||
SubTitle:
|
SubTitle:
|
||||||
"Jika panjang pesan melebihi batas yang ditentukan, pesan tersebut akan dikompresi",
|
"Jika panjang pesan melebihi batas yang ditentukan, pesan tersebut akan dikompresi",
|
||||||
},
|
},
|
||||||
Token: {
|
|
||||||
Title: "Kunci API",
|
|
||||||
SubTitle: "Gunakan kunci Anda untuk melewati batas kode akses",
|
|
||||||
Placeholder: "Kunci API OpenAI",
|
|
||||||
},
|
|
||||||
Usage: {
|
Usage: {
|
||||||
Title: "Saldo Akun",
|
Title: "Saldo Akun",
|
||||||
SubTitle(used: any, total: any) {
|
SubTitle(used: any, total: any) {
|
||||||
@@ -251,15 +248,7 @@ const id: PartialLocaleType = {
|
|||||||
Check: "Periksa",
|
Check: "Periksa",
|
||||||
NoAccess: "Masukkan kunci API untuk memeriksa saldo",
|
NoAccess: "Masukkan kunci API untuk memeriksa saldo",
|
||||||
},
|
},
|
||||||
AccessCode: {
|
|
||||||
Title: "Kode Akses",
|
|
||||||
SubTitle: "Kontrol akses diaktifkan",
|
|
||||||
Placeholder: "Diperlukan kode akses",
|
|
||||||
},
|
|
||||||
Endpoint: {
|
|
||||||
Title: "Endpoint",
|
|
||||||
SubTitle: "Harus dimulai dengan http(s):// untuk endpoint kustom",
|
|
||||||
},
|
|
||||||
Model: "Model",
|
Model: "Model",
|
||||||
Temperature: {
|
Temperature: {
|
||||||
Title: "Suhu",
|
Title: "Suhu",
|
||||||
@@ -301,6 +290,10 @@ const id: PartialLocaleType = {
|
|||||||
Failed:
|
Failed:
|
||||||
"Gagal menyalin, mohon berikan izin untuk mengakses clipboard atau Clipboard API tidak didukung (Tauri)",
|
"Gagal menyalin, mohon berikan izin untuk mengakses clipboard atau Clipboard API tidak didukung (Tauri)",
|
||||||
},
|
},
|
||||||
|
Download: {
|
||||||
|
Success: "Konten berhasil diunduh ke direktori Anda.",
|
||||||
|
Failed: "Unduhan gagal.",
|
||||||
|
},
|
||||||
Context: {
|
Context: {
|
||||||
Toast: (x: any) => `Dengan ${x} promp kontekstual`,
|
Toast: (x: any) => `Dengan ${x} promp kontekstual`,
|
||||||
Edit: "Pengaturan Obrolan Saat Ini",
|
Edit: "Pengaturan Obrolan Saat Ini",
|
||||||
@@ -375,6 +368,9 @@ const id: PartialLocaleType = {
|
|||||||
Edit: "Edit",
|
Edit: "Edit",
|
||||||
},
|
},
|
||||||
Exporter: {
|
Exporter: {
|
||||||
|
Description: {
|
||||||
|
Title: "Hanya pesan setelah menghapus konteks yang akan ditampilkan"
|
||||||
|
},
|
||||||
Model: "Model",
|
Model: "Model",
|
||||||
Messages: "Pesan",
|
Messages: "Pesan",
|
||||||
Topic: "Topik",
|
Topic: "Topik",
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import cn from "./cn";
|
import cn from "./cn";
|
||||||
import en from "./en";
|
import en from "./en";
|
||||||
|
import pt from "./pt";
|
||||||
import tw from "./tw";
|
import tw from "./tw";
|
||||||
import id from "./id";
|
import id from "./id";
|
||||||
import fr from "./fr";
|
import fr from "./fr";
|
||||||
@@ -24,6 +25,7 @@ const ALL_LANGS = {
|
|||||||
cn,
|
cn,
|
||||||
en,
|
en,
|
||||||
tw,
|
tw,
|
||||||
|
pt,
|
||||||
jp,
|
jp,
|
||||||
ko,
|
ko,
|
||||||
id,
|
id,
|
||||||
@@ -47,6 +49,7 @@ export const AllLangs = Object.keys(ALL_LANGS) as Lang[];
|
|||||||
export const ALL_LANG_OPTIONS: Record<Lang, string> = {
|
export const ALL_LANG_OPTIONS: Record<Lang, string> = {
|
||||||
cn: "简体中文",
|
cn: "简体中文",
|
||||||
en: "English",
|
en: "English",
|
||||||
|
pt: "Português",
|
||||||
tw: "繁體中文",
|
tw: "繁體中文",
|
||||||
jp: "日本語",
|
jp: "日本語",
|
||||||
ko: "한국어",
|
ko: "한국어",
|
||||||
|
@@ -124,12 +124,7 @@ const it: PartialLocaleType = {
|
|||||||
SubTitle:
|
SubTitle:
|
||||||
"Comprimerà se la lunghezza dei messaggi non compressi supera il valore",
|
"Comprimerà se la lunghezza dei messaggi non compressi supera il valore",
|
||||||
},
|
},
|
||||||
Token: {
|
|
||||||
Title: "API Key",
|
|
||||||
SubTitle:
|
|
||||||
"Utilizzare la chiave per ignorare il limite del codice di accesso",
|
|
||||||
Placeholder: "OpenAI API Key",
|
|
||||||
},
|
|
||||||
Usage: {
|
Usage: {
|
||||||
Title: "Bilancio Account",
|
Title: "Bilancio Account",
|
||||||
SubTitle(used: any, total: any) {
|
SubTitle(used: any, total: any) {
|
||||||
@@ -139,11 +134,7 @@ const it: PartialLocaleType = {
|
|||||||
Check: "Controlla ancora",
|
Check: "Controlla ancora",
|
||||||
NoAccess: "Inserire la chiave API per controllare il saldo",
|
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",
|
Model: "Modello GPT",
|
||||||
Temperature: {
|
Temperature: {
|
||||||
Title: "Temperature",
|
Title: "Temperature",
|
||||||
|
@@ -20,7 +20,8 @@ const jp: PartialLocaleType = {
|
|||||||
Stop: "停止",
|
Stop: "停止",
|
||||||
Retry: "リトライ",
|
Retry: "リトライ",
|
||||||
Pin: "ピン",
|
Pin: "ピン",
|
||||||
PinToastContent: "コンテキストプロンプトに1つのメッセージをピン留めしました",
|
PinToastContent:
|
||||||
|
"コンテキストプロンプトに1つのメッセージをピン留めしました",
|
||||||
PinToastAction: "表示",
|
PinToastAction: "表示",
|
||||||
Delete: "削除",
|
Delete: "削除",
|
||||||
Edit: "編集",
|
Edit: "編集",
|
||||||
@@ -146,11 +147,7 @@ const jp: PartialLocaleType = {
|
|||||||
SubTitle:
|
SubTitle:
|
||||||
"圧縮されていない履歴メッセージがこの値を超えた場合、圧縮が行われます。",
|
"圧縮されていない履歴メッセージがこの値を超えた場合、圧縮が行われます。",
|
||||||
},
|
},
|
||||||
Token: {
|
|
||||||
Title: "APIキー",
|
|
||||||
SubTitle: "自分のキーを使用してパスワードアクセス制限を迂回する",
|
|
||||||
Placeholder: "OpenAI APIキー",
|
|
||||||
},
|
|
||||||
Usage: {
|
Usage: {
|
||||||
Title: "残高照会",
|
Title: "残高照会",
|
||||||
SubTitle(used: any, total: any) {
|
SubTitle(used: any, total: any) {
|
||||||
@@ -160,11 +157,7 @@ const jp: PartialLocaleType = {
|
|||||||
Check: "再確認",
|
Check: "再確認",
|
||||||
NoAccess: "APIキーまたはアクセスパスワードを入力して残高を表示",
|
NoAccess: "APIキーまたはアクセスパスワードを入力して残高を表示",
|
||||||
},
|
},
|
||||||
AccessCode: {
|
|
||||||
Title: "アクセスパスワード",
|
|
||||||
SubTitle: "暗号化アクセスが有効になっています",
|
|
||||||
Placeholder: "アクセスパスワードを入力してください",
|
|
||||||
},
|
|
||||||
Model: "モデル (model)",
|
Model: "モデル (model)",
|
||||||
Temperature: {
|
Temperature: {
|
||||||
Title: "ランダム性 (temperature)",
|
Title: "ランダム性 (temperature)",
|
||||||
|
@@ -124,11 +124,7 @@ const ko: PartialLocaleType = {
|
|||||||
Title: "기록 압축 임계값",
|
Title: "기록 압축 임계값",
|
||||||
SubTitle: "미압축 메시지 길이가 임계값을 초과하면 압축됨",
|
SubTitle: "미압축 메시지 길이가 임계값을 초과하면 압축됨",
|
||||||
},
|
},
|
||||||
Token: {
|
|
||||||
Title: "API 키",
|
|
||||||
SubTitle: "액세스 코드 제한을 무시하기 위해 키 사용",
|
|
||||||
Placeholder: "OpenAI API 키",
|
|
||||||
},
|
|
||||||
Usage: {
|
Usage: {
|
||||||
Title: "계정 잔액",
|
Title: "계정 잔액",
|
||||||
SubTitle(used: any, total: any) {
|
SubTitle(used: any, total: any) {
|
||||||
@@ -138,11 +134,7 @@ const ko: PartialLocaleType = {
|
|||||||
Check: "확인",
|
Check: "확인",
|
||||||
NoAccess: "잔액 확인을 위해 API 키를 입력하세요.",
|
NoAccess: "잔액 확인을 위해 API 키를 입력하세요.",
|
||||||
},
|
},
|
||||||
AccessCode: {
|
|
||||||
Title: "액세스 코드",
|
|
||||||
SubTitle: "액세스 제어가 활성화됨",
|
|
||||||
Placeholder: "액세스 코드 입력",
|
|
||||||
},
|
|
||||||
Model: "모델",
|
Model: "모델",
|
||||||
Temperature: {
|
Temperature: {
|
||||||
Title: "온도 (temperature)",
|
Title: "온도 (temperature)",
|
||||||
|
@@ -106,12 +106,7 @@ const no: PartialLocaleType = {
|
|||||||
SubTitle:
|
SubTitle:
|
||||||
"Komprimer dersom ikke-komprimert lengde på meldinger overskrider denne verdien",
|
"Komprimer dersom ikke-komprimert lengde på meldinger overskrider denne verdien",
|
||||||
},
|
},
|
||||||
Token: {
|
|
||||||
Title: "API Key",
|
|
||||||
SubTitle:
|
|
||||||
"Bruk din egen API-nøkkel for å ignorere tilgangskoden begrensning",
|
|
||||||
Placeholder: "OpenAI API-nøkkel",
|
|
||||||
},
|
|
||||||
Usage: {
|
Usage: {
|
||||||
Title: "Saldo for konto",
|
Title: "Saldo for konto",
|
||||||
SubTitle(used: any, total: any) {
|
SubTitle(used: any, total: any) {
|
||||||
@@ -121,11 +116,7 @@ const no: PartialLocaleType = {
|
|||||||
Check: "Sjekk",
|
Check: "Sjekk",
|
||||||
NoAccess: "Skriv inn API-nøkkelen for å sjekke saldo",
|
NoAccess: "Skriv inn API-nøkkelen for å sjekke saldo",
|
||||||
},
|
},
|
||||||
AccessCode: {
|
|
||||||
Title: "Tilgangskode",
|
|
||||||
SubTitle: "Tilgangskontroll på",
|
|
||||||
Placeholder: "Trenger tilgangskode",
|
|
||||||
},
|
|
||||||
Model: "Model",
|
Model: "Model",
|
||||||
Temperature: {
|
Temperature: {
|
||||||
Title: "Temperatur",
|
Title: "Temperatur",
|
||||||
|
466
app/locales/pt.ts
Normal file
@@ -0,0 +1,466 @@
|
|||||||
|
import { SubmitKey } from "../store/config";
|
||||||
|
import { PartialLocaleType } from "../locales/index";
|
||||||
|
import { getClientConfig } from "../config/client";
|
||||||
|
|
||||||
|
const isApp = !!getClientConfig()?.isApp;
|
||||||
|
|
||||||
|
const pt: PartialLocaleType = {
|
||||||
|
WIP: "Em breve...",
|
||||||
|
Error: {
|
||||||
|
Unauthorized: isApp
|
||||||
|
? "Chave API inválida, por favor verifique em [Configurações](/#/settings)."
|
||||||
|
: "Acesso não autorizado, por favor insira o código de acesso em [auth](/#/auth) ou insira sua Chave API OpenAI.",
|
||||||
|
},
|
||||||
|
Auth: {
|
||||||
|
Title: "Necessário Código de Acesso",
|
||||||
|
Tips: "Por favor, insira o código de acesso abaixo",
|
||||||
|
SubTips: "Ou insira sua Chave API OpenAI",
|
||||||
|
Input: "código de acesso",
|
||||||
|
Confirm: "Confirmar",
|
||||||
|
Later: "Depois",
|
||||||
|
},
|
||||||
|
ChatItem: {
|
||||||
|
ChatItemCount: (count: number) => `${count} mensagens`,
|
||||||
|
},
|
||||||
|
Chat: {
|
||||||
|
SubTitle: (count: number) => `${count} mensagens`,
|
||||||
|
EditMessage: {
|
||||||
|
Title: "Editar Todas as Mensagens",
|
||||||
|
Topic: {
|
||||||
|
Title: "Tópico",
|
||||||
|
SubTitle: "Mudar o tópico atual",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Actions: {
|
||||||
|
ChatList: "Ir Para Lista de Chat",
|
||||||
|
CompressedHistory: "Prompt de Memória Histórica Comprimida",
|
||||||
|
Export: "Exportar Todas as Mensagens como Markdown",
|
||||||
|
Copy: "Copiar",
|
||||||
|
Stop: "Parar",
|
||||||
|
Retry: "Tentar Novamente",
|
||||||
|
Pin: "Fixar",
|
||||||
|
PinToastContent: "Fixada 1 mensagem para prompts contextuais",
|
||||||
|
PinToastAction: "Visualizar",
|
||||||
|
Delete: "Deletar",
|
||||||
|
Edit: "Editar",
|
||||||
|
},
|
||||||
|
Commands: {
|
||||||
|
new: "Iniciar um novo chat",
|
||||||
|
newm: "Iniciar um novo chat com máscara",
|
||||||
|
next: "Próximo Chat",
|
||||||
|
prev: "Chat Anterior",
|
||||||
|
clear: "Limpar Contexto",
|
||||||
|
del: "Deletar Chat",
|
||||||
|
},
|
||||||
|
InputActions: {
|
||||||
|
Stop: "Parar",
|
||||||
|
ToBottom: "Para o Mais Recente",
|
||||||
|
Theme: {
|
||||||
|
auto: "Automático",
|
||||||
|
light: "Tema Claro",
|
||||||
|
dark: "Tema Escuro",
|
||||||
|
},
|
||||||
|
Prompt: "Prompts",
|
||||||
|
Masks: "Máscaras",
|
||||||
|
Clear: "Limpar Contexto",
|
||||||
|
Settings: "Configurações",
|
||||||
|
},
|
||||||
|
Rename: "Renomear Chat",
|
||||||
|
Typing: "Digitando…",
|
||||||
|
Input: (submitKey: string) => {
|
||||||
|
var inputHints = `${submitKey} para enviar`;
|
||||||
|
if (submitKey === String(SubmitKey.Enter)) {
|
||||||
|
inputHints += ", Shift + Enter para quebrar linha";
|
||||||
|
}
|
||||||
|
return inputHints + ", / para buscar prompts, : para usar comandos";
|
||||||
|
},
|
||||||
|
Send: "Enviar",
|
||||||
|
Config: {
|
||||||
|
Reset: "Redefinir para Padrão",
|
||||||
|
SaveAs: "Salvar como Máscara",
|
||||||
|
},
|
||||||
|
IsContext: "Prompt Contextual",
|
||||||
|
},
|
||||||
|
Export: {
|
||||||
|
Title: "Exportar Mensagens",
|
||||||
|
Copy: "Copiar Tudo",
|
||||||
|
Download: "Baixar",
|
||||||
|
MessageFromYou: "Mensagem De Você",
|
||||||
|
MessageFromChatGPT: "Mensagem De ChatGPT",
|
||||||
|
Share: "Compartilhar para ShareGPT",
|
||||||
|
Format: {
|
||||||
|
Title: "Formato de Exportação",
|
||||||
|
SubTitle: "Markdown ou Imagem PNG",
|
||||||
|
},
|
||||||
|
IncludeContext: {
|
||||||
|
Title: "Incluindo Contexto",
|
||||||
|
SubTitle: "Exportar prompts de contexto na máscara ou não",
|
||||||
|
},
|
||||||
|
Steps: {
|
||||||
|
Select: "Selecionar",
|
||||||
|
Preview: "Pré-visualizar",
|
||||||
|
},
|
||||||
|
Image: {
|
||||||
|
Toast: "Capturando Imagem...",
|
||||||
|
Modal:
|
||||||
|
"Pressione longamente ou clique com o botão direito para salvar a imagem",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Select: {
|
||||||
|
Search: "Buscar",
|
||||||
|
All: "Selecionar Tudo",
|
||||||
|
Latest: "Selecionar Mais Recente",
|
||||||
|
Clear: "Limpar",
|
||||||
|
},
|
||||||
|
Memory: {
|
||||||
|
Title: "Prompt de Memória",
|
||||||
|
EmptyContent: "Nada ainda.",
|
||||||
|
Send: "Enviar Memória",
|
||||||
|
Copy: "Copiar Memória",
|
||||||
|
Reset: "Resetar Sessão",
|
||||||
|
ResetConfirm:
|
||||||
|
"Resetar irá limpar o histórico de conversa atual e a memória histórica. Você tem certeza que quer resetar?",
|
||||||
|
},
|
||||||
|
Home: {
|
||||||
|
NewChat: "Novo Chat",
|
||||||
|
DeleteChat: "Confirmar para deletar a conversa selecionada?",
|
||||||
|
DeleteToast: "Chat Deletado",
|
||||||
|
Revert: "Reverter",
|
||||||
|
},
|
||||||
|
Settings: {
|
||||||
|
Title: "Configurações",
|
||||||
|
SubTitle: "Todas as Configurações",
|
||||||
|
Danger: {
|
||||||
|
Reset: {
|
||||||
|
Title: "Resetar Todas as Configurações",
|
||||||
|
SubTitle: "Resetar todos os itens de configuração para o padrão",
|
||||||
|
Action: "Resetar",
|
||||||
|
Confirm: "Confirmar para resetar todas as configurações para o padrão?",
|
||||||
|
},
|
||||||
|
Clear: {
|
||||||
|
Title: "Limpar Todos os Dados",
|
||||||
|
SubTitle: "Limpar todas as mensagens e configurações",
|
||||||
|
Action: "Limpar",
|
||||||
|
Confirm: "Confirmar para limpar todas as mensagens e configurações?",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Lang: {
|
||||||
|
Name: "Language",
|
||||||
|
All: "Todos os Idiomas",
|
||||||
|
},
|
||||||
|
Avatar: "Avatar",
|
||||||
|
FontSize: {
|
||||||
|
Title: "Tamanho da Fonte",
|
||||||
|
SubTitle: "Ajustar o tamanho da fonte do conteúdo do chat",
|
||||||
|
},
|
||||||
|
InjectSystemPrompts: {
|
||||||
|
Title: "Inserir Prompts de Sistema",
|
||||||
|
SubTitle: "Inserir um prompt de sistema global para cada requisição",
|
||||||
|
},
|
||||||
|
InputTemplate: {
|
||||||
|
Title: "Modelo de Entrada",
|
||||||
|
SubTitle: "A mensagem mais recente será preenchida neste modelo",
|
||||||
|
},
|
||||||
|
|
||||||
|
Update: {
|
||||||
|
Version: (x: string) => `Versão: ${x}`,
|
||||||
|
IsLatest: "Última versão",
|
||||||
|
CheckUpdate: "Verificar Atualização",
|
||||||
|
IsChecking: "Verificando atualização...",
|
||||||
|
FoundUpdate: (x: string) => `Nova versão encontrada: ${x}`,
|
||||||
|
GoToUpdate: "Atualizar",
|
||||||
|
},
|
||||||
|
SendKey: "Tecla de Envio",
|
||||||
|
Theme: "Tema",
|
||||||
|
TightBorder: "Borda Ajustada",
|
||||||
|
SendPreviewBubble: {
|
||||||
|
Title: "Bolha de Pré-visualização de Envio",
|
||||||
|
SubTitle: "Pré-visualizar markdown na bolha",
|
||||||
|
},
|
||||||
|
AutoGenerateTitle: {
|
||||||
|
Title: "Gerar Título Automaticamente",
|
||||||
|
SubTitle: "Gerar um título adequado baseado no conteúdo da conversa",
|
||||||
|
},
|
||||||
|
Sync: {
|
||||||
|
CloudState: "Última Atualização",
|
||||||
|
NotSyncYet: "Ainda não sincronizado",
|
||||||
|
Success: "Sincronização bem sucedida",
|
||||||
|
Fail: "Falha na sincronização",
|
||||||
|
|
||||||
|
Config: {
|
||||||
|
Modal: {
|
||||||
|
Title: "Configurar Sincronização",
|
||||||
|
Check: "Verificar Conexão",
|
||||||
|
},
|
||||||
|
SyncType: {
|
||||||
|
Title: "Tipo de Sincronização",
|
||||||
|
SubTitle: "Escolha seu serviço de sincronização favorito",
|
||||||
|
},
|
||||||
|
Proxy: {
|
||||||
|
Title: "Habilitar Proxy CORS",
|
||||||
|
SubTitle: "Habilitar um proxy para evitar restrições de cross-origin",
|
||||||
|
},
|
||||||
|
ProxyUrl: {
|
||||||
|
Title: "Endpoint de Proxy",
|
||||||
|
SubTitle: "Apenas aplicável ao proxy CORS embutido para este projeto",
|
||||||
|
},
|
||||||
|
|
||||||
|
WebDav: {
|
||||||
|
Endpoint: "Endpoint WebDAV",
|
||||||
|
UserName: "Nome de Usuário",
|
||||||
|
Password: "Senha",
|
||||||
|
},
|
||||||
|
|
||||||
|
UpStash: {
|
||||||
|
Endpoint: "URL REST Redis UpStash",
|
||||||
|
UserName: "Nome do Backup",
|
||||||
|
Password: "Token REST Redis UpStash",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
LocalState: "Dados Locais",
|
||||||
|
Overview: (overview: any) => {
|
||||||
|
return `${overview.chat} chats,${overview.message} mensagens,${overview.prompt} prompts,${overview.mask} máscaras`;
|
||||||
|
},
|
||||||
|
ImportFailed: "Falha ao importar do arquivo",
|
||||||
|
},
|
||||||
|
Mask: {
|
||||||
|
Splash: {
|
||||||
|
Title: "Tela de Início da Máscara",
|
||||||
|
SubTitle:
|
||||||
|
"Mostrar uma tela de início da máscara antes de iniciar novo chat",
|
||||||
|
},
|
||||||
|
Builtin: {
|
||||||
|
Title: "Esconder Máscaras Embutidas",
|
||||||
|
SubTitle: "Esconder máscaras embutidas na lista de máscaras",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Prompt: {
|
||||||
|
Disable: {
|
||||||
|
Title: "Desabilitar auto-completar",
|
||||||
|
SubTitle: "Digite / para acionar auto-completar",
|
||||||
|
},
|
||||||
|
List: "Lista de Prompts",
|
||||||
|
ListCount: (builtin: number, custom: number) =>
|
||||||
|
`${builtin} embutidos, ${custom} definidos pelo usuário`,
|
||||||
|
Edit: "Editar",
|
||||||
|
Modal: {
|
||||||
|
Title: "Lista de Prompts",
|
||||||
|
Add: "Adicionar Um",
|
||||||
|
Search: "Buscar Prompts",
|
||||||
|
},
|
||||||
|
EditModal: {
|
||||||
|
Title: "Editar Prompt",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
HistoryCount: {
|
||||||
|
Title: "Contagem de Mensagens Anexadas",
|
||||||
|
SubTitle: "Número de mensagens enviadas anexadas por requisição",
|
||||||
|
},
|
||||||
|
CompressThreshold: {
|
||||||
|
Title: "Limite de Compressão de Histórico",
|
||||||
|
SubTitle:
|
||||||
|
"Irá comprimir se o comprimento das mensagens não comprimidas exceder o valor",
|
||||||
|
},
|
||||||
|
|
||||||
|
Usage: {
|
||||||
|
Title: "Saldo da Conta",
|
||||||
|
SubTitle(used: any, total: any) {
|
||||||
|
return `Usado este mês ${used}, assinatura ${total}`;
|
||||||
|
},
|
||||||
|
IsChecking: "Verificando...",
|
||||||
|
Check: "Verificar",
|
||||||
|
NoAccess: "Insira a Chave API para verificar o saldo",
|
||||||
|
},
|
||||||
|
Access: {
|
||||||
|
AccessCode: {
|
||||||
|
Title: "Código de Acesso",
|
||||||
|
SubTitle: "Controle de Acesso Habilitado",
|
||||||
|
Placeholder: "Insira o Código",
|
||||||
|
},
|
||||||
|
CustomEndpoint: {
|
||||||
|
Title: "Endpoint Personalizado",
|
||||||
|
SubTitle: "Use serviço personalizado Azure ou OpenAI",
|
||||||
|
},
|
||||||
|
Provider: {
|
||||||
|
Title: "Provedor do Modelo",
|
||||||
|
SubTitle: "Selecione Azure ou OpenAI",
|
||||||
|
},
|
||||||
|
OpenAI: {
|
||||||
|
ApiKey: {
|
||||||
|
Title: "Chave API OpenAI",
|
||||||
|
SubTitle: "Usar Chave API OpenAI personalizada",
|
||||||
|
Placeholder: "sk-xxx",
|
||||||
|
},
|
||||||
|
|
||||||
|
Endpoint: {
|
||||||
|
Title: "Endpoint OpenAI",
|
||||||
|
SubTitle:
|
||||||
|
"Deve começar com http(s):// ou usar /api/openai como padrão",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Azure: {
|
||||||
|
ApiKey: {
|
||||||
|
Title: "Chave API Azure",
|
||||||
|
SubTitle: "Verifique sua chave API do console Azure",
|
||||||
|
Placeholder: "Chave API Azure",
|
||||||
|
},
|
||||||
|
|
||||||
|
Endpoint: {
|
||||||
|
Title: "Endpoint Azure",
|
||||||
|
SubTitle: "Exemplo: ",
|
||||||
|
},
|
||||||
|
|
||||||
|
ApiVerion: {
|
||||||
|
Title: "Versão API Azure",
|
||||||
|
SubTitle: "Verifique sua versão API do console Azure",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
CustomModel: {
|
||||||
|
Title: "Modelos Personalizados",
|
||||||
|
SubTitle: "Opções de modelo personalizado, separados por vírgula",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
Model: "Modelo",
|
||||||
|
Temperature: {
|
||||||
|
Title: "Temperatura",
|
||||||
|
SubTitle: "Um valor maior torna a saída mais aleatória",
|
||||||
|
},
|
||||||
|
TopP: {
|
||||||
|
Title: "Top P",
|
||||||
|
SubTitle: "Não altere este valor junto com a temperatura",
|
||||||
|
},
|
||||||
|
MaxTokens: {
|
||||||
|
Title: "Máximo de Tokens",
|
||||||
|
SubTitle: "Comprimento máximo de tokens de entrada e tokens gerados",
|
||||||
|
},
|
||||||
|
PresencePenalty: {
|
||||||
|
Title: "Penalidade de Presença",
|
||||||
|
SubTitle:
|
||||||
|
"Um valor maior aumenta a probabilidade de falar sobre novos tópicos",
|
||||||
|
},
|
||||||
|
FrequencyPenalty: {
|
||||||
|
Title: "Penalidade de Frequência",
|
||||||
|
SubTitle:
|
||||||
|
"Um valor maior diminui a probabilidade de repetir a mesma linha",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Store: {
|
||||||
|
DefaultTopic: "Nova Conversa",
|
||||||
|
BotHello: "Olá! Como posso ajudá-lo hoje?",
|
||||||
|
Error: "Algo deu errado, por favor tente novamente mais tarde.",
|
||||||
|
Prompt: {
|
||||||
|
History: (content: string) =>
|
||||||
|
"Este é um resumo do histórico de chat como um recapitulativo: " +
|
||||||
|
content,
|
||||||
|
Topic:
|
||||||
|
"Por favor, gere um título de quatro a cinco palavras resumindo nossa conversa sem qualquer introdução, pontuação, aspas, períodos, símbolos ou texto adicional. Remova as aspas que o envolvem.",
|
||||||
|
Summarize:
|
||||||
|
"Resuma a discussão brevemente em 200 palavras ou menos para usar como um prompt para o contexto futuro.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Copy: {
|
||||||
|
Success: "Copiado para a área de transferência",
|
||||||
|
Failed:
|
||||||
|
"Falha na cópia, por favor conceda permissão para acessar a área de transferência",
|
||||||
|
},
|
||||||
|
Download: {
|
||||||
|
Success: "Conteúdo baixado para seu diretório.",
|
||||||
|
Failed: "Falha no download.",
|
||||||
|
},
|
||||||
|
Context: {
|
||||||
|
Toast: (x: any) => `Com ${x} prompts contextuais`,
|
||||||
|
Edit: "Configurações do Chat Atual",
|
||||||
|
Add: "Adicionar um Prompt",
|
||||||
|
Clear: "Contexto Limpo",
|
||||||
|
Revert: "Reverter",
|
||||||
|
},
|
||||||
|
Plugin: {
|
||||||
|
Name: "Plugin",
|
||||||
|
},
|
||||||
|
FineTuned: {
|
||||||
|
Sysmessage: "Você é um assistente que",
|
||||||
|
},
|
||||||
|
Mask: {
|
||||||
|
Name: "Máscara",
|
||||||
|
Page: {
|
||||||
|
Title: "Template de Prompt",
|
||||||
|
SubTitle: (count: number) => `${count} templates de prompt`,
|
||||||
|
Search: "Buscar Templates",
|
||||||
|
Create: "Criar",
|
||||||
|
},
|
||||||
|
Item: {
|
||||||
|
Info: (count: number) => `${count} prompts`,
|
||||||
|
Chat: "Chat",
|
||||||
|
View: "Visualizar",
|
||||||
|
Edit: "Editar",
|
||||||
|
Delete: "Deletar",
|
||||||
|
DeleteConfirm: "Confirmar para deletar?",
|
||||||
|
},
|
||||||
|
EditModal: {
|
||||||
|
Title: (readonly: boolean) =>
|
||||||
|
`Editar Template de Prompt ${readonly ? "(somente leitura)" : ""}`,
|
||||||
|
Download: "Baixar",
|
||||||
|
Clone: "Clonar",
|
||||||
|
},
|
||||||
|
Config: {
|
||||||
|
Avatar: "Avatar do Bot",
|
||||||
|
Name: "Nome do Bot",
|
||||||
|
Sync: {
|
||||||
|
Title: "Usar Configuração Global",
|
||||||
|
SubTitle: "Usar configuração global neste chat",
|
||||||
|
Confirm:
|
||||||
|
"Confirmar para substituir a configuração personalizada pela configuração global?",
|
||||||
|
},
|
||||||
|
HideContext: {
|
||||||
|
Title: "Esconder Prompts de Contexto",
|
||||||
|
SubTitle: "Não mostrar prompts de contexto no chat",
|
||||||
|
},
|
||||||
|
Share: {
|
||||||
|
Title: "Compartilhar Esta Máscara",
|
||||||
|
SubTitle: "Gerar um link para esta máscara",
|
||||||
|
Action: "Copiar Link",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
NewChat: {
|
||||||
|
Return: "Retornar",
|
||||||
|
Skip: "Apenas Começar",
|
||||||
|
Title: "Escolher uma Máscara",
|
||||||
|
SubTitle: "Converse com a Alma por trás da Máscara",
|
||||||
|
More: "Encontre Mais",
|
||||||
|
NotShow: "Nunca Mostrar Novamente",
|
||||||
|
ConfirmNoShow:
|
||||||
|
"Confirmar para desabilitar?Você pode habilitar nas configurações depois.",
|
||||||
|
},
|
||||||
|
|
||||||
|
UI: {
|
||||||
|
Confirm: "Confirmar",
|
||||||
|
Cancel: "Cancelar",
|
||||||
|
Close: "Fechar",
|
||||||
|
Create: "Criar",
|
||||||
|
Edit: "Editar",
|
||||||
|
Export: "Exportar",
|
||||||
|
Import: "Importar",
|
||||||
|
Sync: "Sincronizar",
|
||||||
|
Config: "Configurar",
|
||||||
|
},
|
||||||
|
Exporter: {
|
||||||
|
Description: {
|
||||||
|
Title: "Apenas mensagens após a limpeza do contexto serão exibidas",
|
||||||
|
},
|
||||||
|
Model: "Modelo",
|
||||||
|
Messages: "Mensagens",
|
||||||
|
Topic: "Tópico",
|
||||||
|
Time: "Tempo",
|
||||||
|
},
|
||||||
|
|
||||||
|
URLCommand: {
|
||||||
|
Code: "Código de acesso detectado a partir da url, confirmar para aplicar? ",
|
||||||
|
Settings:
|
||||||
|
"Configurações detectadas a partir da url, confirmar para aplicar?",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default pt;
|
@@ -125,11 +125,7 @@ const ru: PartialLocaleType = {
|
|||||||
SubTitle:
|
SubTitle:
|
||||||
"Будет сжимать, если длина несжатых сообщений превышает указанное значение",
|
"Будет сжимать, если длина несжатых сообщений превышает указанное значение",
|
||||||
},
|
},
|
||||||
Token: {
|
|
||||||
Title: "API ключ",
|
|
||||||
SubTitle: "Используйте свой ключ, чтобы игнорировать лимит доступа",
|
|
||||||
Placeholder: "API ключ OpenAI",
|
|
||||||
},
|
|
||||||
Usage: {
|
Usage: {
|
||||||
Title: "Баланс аккаунта",
|
Title: "Баланс аккаунта",
|
||||||
SubTitle(used: any, total: any) {
|
SubTitle(used: any, total: any) {
|
||||||
@@ -139,11 +135,7 @@ const ru: PartialLocaleType = {
|
|||||||
Check: "Проверить",
|
Check: "Проверить",
|
||||||
NoAccess: "Введите API ключ, чтобы проверить баланс",
|
NoAccess: "Введите API ключ, чтобы проверить баланс",
|
||||||
},
|
},
|
||||||
AccessCode: {
|
|
||||||
Title: "Код доступа",
|
|
||||||
SubTitle: "Контроль доступа включен",
|
|
||||||
Placeholder: "Требуется код доступа",
|
|
||||||
},
|
|
||||||
Model: "Модель",
|
Model: "Модель",
|
||||||
Temperature: {
|
Temperature: {
|
||||||
Title: "Температура",
|
Title: "Температура",
|
||||||
|
@@ -124,11 +124,7 @@ const tr: PartialLocaleType = {
|
|||||||
SubTitle:
|
SubTitle:
|
||||||
"Sıkıştırılmamış mesajların uzunluğu bu değeri aşarsa sıkıştırılır",
|
"Sıkıştırılmamış mesajların uzunluğu bu değeri aşarsa sıkıştırılır",
|
||||||
},
|
},
|
||||||
Token: {
|
|
||||||
Title: "API Anahtarı",
|
|
||||||
SubTitle: "Erişim kodu sınırını yoksaymak için anahtarınızı kullanın",
|
|
||||||
Placeholder: "OpenAI API Anahtarı",
|
|
||||||
},
|
|
||||||
Usage: {
|
Usage: {
|
||||||
Title: "Hesap Bakiyesi",
|
Title: "Hesap Bakiyesi",
|
||||||
SubTitle(used: any, total: any) {
|
SubTitle(used: any, total: any) {
|
||||||
@@ -138,11 +134,7 @@ const tr: PartialLocaleType = {
|
|||||||
Check: "Tekrar Kontrol Et",
|
Check: "Tekrar Kontrol Et",
|
||||||
NoAccess: "Bakiyeyi kontrol etmek için API anahtarını girin",
|
NoAccess: "Bakiyeyi kontrol etmek için API anahtarını girin",
|
||||||
},
|
},
|
||||||
AccessCode: {
|
|
||||||
Title: "Erişim Kodu",
|
|
||||||
SubTitle: "Erişim kontrolü etkinleştirme",
|
|
||||||
Placeholder: "Erişim Kodu Gerekiyor",
|
|
||||||
},
|
|
||||||
Model: "Model",
|
Model: "Model",
|
||||||
Temperature: {
|
Temperature: {
|
||||||
Title: "Gerçeklik",
|
Title: "Gerçeklik",
|
||||||
|
@@ -7,13 +7,13 @@ const tw: PartialLocaleType = {
|
|||||||
Unauthorized: "目前您的狀態是未授權,請前往[設定頁面](/#/auth)輸入授權碼。",
|
Unauthorized: "目前您的狀態是未授權,請前往[設定頁面](/#/auth)輸入授權碼。",
|
||||||
},
|
},
|
||||||
ChatItem: {
|
ChatItem: {
|
||||||
ChatItemCount: (count: number) => `${count} 條對話`,
|
ChatItemCount: (count: number) => `${count} 則對話`,
|
||||||
},
|
},
|
||||||
Chat: {
|
Chat: {
|
||||||
SubTitle: (count: number) => `您已經與 ChatGPT 進行了 ${count} 條對話`,
|
SubTitle: (count: number) => `您已經與 ChatGPT 進行了 ${count} 則對話`,
|
||||||
Actions: {
|
Actions: {
|
||||||
ChatList: "查看訊息列表",
|
ChatList: "檢視訊息列表",
|
||||||
CompressedHistory: "查看壓縮後的歷史 Prompt",
|
CompressedHistory: "檢視壓縮後的歷史 Prompt",
|
||||||
Export: "匯出聊天紀錄",
|
Export: "匯出聊天紀錄",
|
||||||
Copy: "複製",
|
Copy: "複製",
|
||||||
Stop: "停止",
|
Stop: "停止",
|
||||||
@@ -23,15 +23,15 @@ const tw: PartialLocaleType = {
|
|||||||
Rename: "重新命名對話",
|
Rename: "重新命名對話",
|
||||||
Typing: "正在輸入…",
|
Typing: "正在輸入…",
|
||||||
Input: (submitKey: string) => {
|
Input: (submitKey: string) => {
|
||||||
var inputHints = `輸入訊息後,按下 ${submitKey} 鍵即可發送`;
|
var inputHints = `輸入訊息後,按下 ${submitKey} 鍵即可傳送`;
|
||||||
if (submitKey === String(SubmitKey.Enter)) {
|
if (submitKey === String(SubmitKey.Enter)) {
|
||||||
inputHints += ",Shift + Enter 鍵換行";
|
inputHints += ",Shift + Enter 鍵換行";
|
||||||
}
|
}
|
||||||
return inputHints;
|
return inputHints;
|
||||||
},
|
},
|
||||||
Send: "發送",
|
Send: "傳送",
|
||||||
Config: {
|
Config: {
|
||||||
Reset: "重置預設",
|
Reset: "重設",
|
||||||
SaveAs: "另存新檔",
|
SaveAs: "另存新檔",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -46,7 +46,7 @@ const tw: PartialLocaleType = {
|
|||||||
Title: "上下文記憶 Prompt",
|
Title: "上下文記憶 Prompt",
|
||||||
EmptyContent: "尚未記憶",
|
EmptyContent: "尚未記憶",
|
||||||
Copy: "複製全部",
|
Copy: "複製全部",
|
||||||
Send: "發送記憶",
|
Send: "傳送記憶",
|
||||||
Reset: "重設對話",
|
Reset: "重設對話",
|
||||||
ResetConfirm: "重設後將清除目前對話記錄以及歷史記憶,確認重設?",
|
ResetConfirm: "重設後將清除目前對話記錄以及歷史記憶,確認重設?",
|
||||||
},
|
},
|
||||||
@@ -71,22 +71,22 @@ const tw: PartialLocaleType = {
|
|||||||
},
|
},
|
||||||
InjectSystemPrompts: {
|
InjectSystemPrompts: {
|
||||||
Title: "匯入系統提示",
|
Title: "匯入系統提示",
|
||||||
SubTitle: "強制在每個請求的訊息列表開頭添加一個模擬 ChatGPT 的系統提示",
|
SubTitle: "強制在每個請求的訊息列表開頭新增一個模擬 ChatGPT 的系統提示",
|
||||||
},
|
},
|
||||||
Update: {
|
Update: {
|
||||||
Version: (x: string) => `當前版本:${x}`,
|
Version: (x: string) => `目前版本:${x}`,
|
||||||
IsLatest: "已是最新版本",
|
IsLatest: "已是最新版本",
|
||||||
CheckUpdate: "檢查更新",
|
CheckUpdate: "檢查更新",
|
||||||
IsChecking: "正在檢查更新...",
|
IsChecking: "正在檢查更新...",
|
||||||
FoundUpdate: (x: string) => `發現新版本:${x}`,
|
FoundUpdate: (x: string) => `發現新版本:${x}`,
|
||||||
GoToUpdate: "前往更新",
|
GoToUpdate: "前往更新",
|
||||||
},
|
},
|
||||||
SendKey: "發送鍵",
|
SendKey: "傳送鍵",
|
||||||
Theme: "主題",
|
Theme: "主題",
|
||||||
TightBorder: "緊湊邊框",
|
TightBorder: "緊湊邊框",
|
||||||
SendPreviewBubble: {
|
SendPreviewBubble: {
|
||||||
Title: "預覽氣泡",
|
Title: "預覽氣泡",
|
||||||
SubTitle: "在預覽氣泡中預覽 Markdown 内容",
|
SubTitle: "在預覽氣泡中預覽 Markdown 內容",
|
||||||
},
|
},
|
||||||
Mask: {
|
Mask: {
|
||||||
Splash: {
|
Splash: {
|
||||||
@@ -101,7 +101,7 @@ const tw: PartialLocaleType = {
|
|||||||
},
|
},
|
||||||
List: "自定義提示詞列表",
|
List: "自定義提示詞列表",
|
||||||
ListCount: (builtin: number, custom: number) =>
|
ListCount: (builtin: number, custom: number) =>
|
||||||
`內建 ${builtin} 條,用戶定義 ${custom} 條`,
|
`內建 ${builtin} 條,使用者定義 ${custom} 條`,
|
||||||
Edit: "編輯",
|
Edit: "編輯",
|
||||||
Modal: {
|
Modal: {
|
||||||
Title: "提示詞列表",
|
Title: "提示詞列表",
|
||||||
@@ -120,11 +120,7 @@ const tw: PartialLocaleType = {
|
|||||||
Title: "歷史訊息長度壓縮閾值",
|
Title: "歷史訊息長度壓縮閾值",
|
||||||
SubTitle: "當未壓縮的歷史訊息超過該值時,將進行壓縮",
|
SubTitle: "當未壓縮的歷史訊息超過該值時,將進行壓縮",
|
||||||
},
|
},
|
||||||
Token: {
|
|
||||||
Title: "API Key",
|
|
||||||
SubTitle: "使用自己的 Key 可規避授權存取限制",
|
|
||||||
Placeholder: "OpenAI API Key",
|
|
||||||
},
|
|
||||||
Usage: {
|
Usage: {
|
||||||
Title: "帳戶餘額",
|
Title: "帳戶餘額",
|
||||||
SubTitle(used: any, total: any) {
|
SubTitle(used: any, total: any) {
|
||||||
@@ -132,13 +128,9 @@ const tw: PartialLocaleType = {
|
|||||||
},
|
},
|
||||||
IsChecking: "正在檢查…",
|
IsChecking: "正在檢查…",
|
||||||
Check: "重新檢查",
|
Check: "重新檢查",
|
||||||
NoAccess: "輸入API Key查看餘額",
|
NoAccess: "輸入 API Key 檢視餘額",
|
||||||
},
|
|
||||||
AccessCode: {
|
|
||||||
Title: "授權碼",
|
|
||||||
SubTitle: "目前是未授權存取狀態",
|
|
||||||
Placeholder: "請輸入授權碼",
|
|
||||||
},
|
},
|
||||||
|
|
||||||
Model: "模型 (model)",
|
Model: "模型 (model)",
|
||||||
Temperature: {
|
Temperature: {
|
||||||
Title: "隨機性 (temperature)",
|
Title: "隨機性 (temperature)",
|
||||||
@@ -150,7 +142,7 @@ const tw: PartialLocaleType = {
|
|||||||
},
|
},
|
||||||
PresencePenalty: {
|
PresencePenalty: {
|
||||||
Title: "話題新穎度 (presence_penalty)",
|
Title: "話題新穎度 (presence_penalty)",
|
||||||
SubTitle: "值越大,越有可能擴展到新話題",
|
SubTitle: "值越大,越有可能拓展到新話題",
|
||||||
},
|
},
|
||||||
FrequencyPenalty: {
|
FrequencyPenalty: {
|
||||||
Title: "頻率懲罰度 (frequency_penalty)",
|
Title: "頻率懲罰度 (frequency_penalty)",
|
||||||
@@ -163,7 +155,7 @@ const tw: PartialLocaleType = {
|
|||||||
Error: "出錯了,請稍後再嘗試",
|
Error: "出錯了,請稍後再嘗試",
|
||||||
Prompt: {
|
Prompt: {
|
||||||
History: (content: string) =>
|
History: (content: string) =>
|
||||||
"這是 AI 與用戶的歷史聊天總結,作為前情提要:" + content,
|
"這是 AI 與使用者的歷史聊天總結,作為前情提要:" + content,
|
||||||
Topic:
|
Topic:
|
||||||
"Use the language used by the user (e.g. en for english conversation, zh-hant for chinese conversation, etc.) to generate a title (at most 6 words) summarizing our conversation without any lead-in, quotation marks, preamble like 'Title:', direct text copies, single-word replies, quotation marks, translations, or brackets. Remove enclosing quotation marks. The title should make third-party grasp the essence of the conversation in first sight.",
|
"Use the language used by the user (e.g. en for english conversation, zh-hant for chinese conversation, etc.) to generate a title (at most 6 words) summarizing our conversation without any lead-in, quotation marks, preamble like 'Title:', direct text copies, single-word replies, quotation marks, translations, or brackets. Remove enclosing quotation marks. The title should make third-party grasp the essence of the conversation in first sight.",
|
||||||
Summarize:
|
Summarize:
|
||||||
@@ -192,16 +184,16 @@ const tw: PartialLocaleType = {
|
|||||||
Item: {
|
Item: {
|
||||||
Info: (count: number) => `包含 ${count} 條預設對話`,
|
Info: (count: number) => `包含 ${count} 條預設對話`,
|
||||||
Chat: "對話",
|
Chat: "對話",
|
||||||
View: "查看",
|
View: "檢視",
|
||||||
Edit: "編輯",
|
Edit: "編輯",
|
||||||
Delete: "删除",
|
Delete: "刪除",
|
||||||
DeleteConfirm: "確認删除?",
|
DeleteConfirm: "確認刪除?",
|
||||||
},
|
},
|
||||||
EditModal: {
|
EditModal: {
|
||||||
Title: (readonly: boolean) =>
|
Title: (readonly: boolean) =>
|
||||||
`編輯預設面具 ${readonly ? "(只读)" : ""}`,
|
`編輯預設面具 ${readonly ? "(只讀)" : ""}`,
|
||||||
Download: "下載預設",
|
Download: "下載預設",
|
||||||
Clone: "克隆預設",
|
Clone: "複製預設",
|
||||||
},
|
},
|
||||||
Config: {
|
Config: {
|
||||||
Avatar: "角色頭像",
|
Avatar: "角色頭像",
|
||||||
@@ -215,7 +207,7 @@ const tw: PartialLocaleType = {
|
|||||||
SubTitle: "現在開始,與面具背後的靈魂思維碰撞",
|
SubTitle: "現在開始,與面具背後的靈魂思維碰撞",
|
||||||
More: "搜尋更多",
|
More: "搜尋更多",
|
||||||
NotShow: "不再呈現",
|
NotShow: "不再呈現",
|
||||||
ConfirmNoShow: "確認禁用?禁用後可以随時在設定中重新啟用。",
|
ConfirmNoShow: "確認停用?停用後可以隨時在設定中重新啟用。",
|
||||||
},
|
},
|
||||||
UI: {
|
UI: {
|
||||||
Confirm: "確認",
|
Confirm: "確認",
|
||||||
|
@@ -123,11 +123,7 @@ const vi: PartialLocaleType = {
|
|||||||
Title: "Ngưỡng nén lịch sử tin nhắn",
|
Title: "Ngưỡng nén lịch sử tin nhắn",
|
||||||
SubTitle: "Thực hiện nén nếu số lượng tin nhắn chưa nén vượt quá ngưỡng",
|
SubTitle: "Thực hiện nén nếu số lượng tin nhắn chưa nén vượt quá ngưỡng",
|
||||||
},
|
},
|
||||||
Token: {
|
|
||||||
Title: "API Key",
|
|
||||||
SubTitle: "Sử dụng khóa của bạn để bỏ qua giới hạn mã truy cập",
|
|
||||||
Placeholder: "OpenAI API Key",
|
|
||||||
},
|
|
||||||
Usage: {
|
Usage: {
|
||||||
Title: "Hạn mức tài khoản",
|
Title: "Hạn mức tài khoản",
|
||||||
SubTitle(used: any, total: any) {
|
SubTitle(used: any, total: any) {
|
||||||
@@ -137,11 +133,7 @@ const vi: PartialLocaleType = {
|
|||||||
Check: "Kiểm tra",
|
Check: "Kiểm tra",
|
||||||
NoAccess: "Nhập API Key để kiểm tra hạn mức",
|
NoAccess: "Nhập API Key để kiểm tra hạn mức",
|
||||||
},
|
},
|
||||||
AccessCode: {
|
|
||||||
Title: "Mã truy cập",
|
|
||||||
SubTitle: "Đã bật kiểm soát truy cập",
|
|
||||||
Placeholder: "Nhập mã truy cập",
|
|
||||||
},
|
|
||||||
Model: "Mô hình",
|
Model: "Mô hình",
|
||||||
Temperature: {
|
Temperature: {
|
||||||
Title: "Tính ngẫu nhiên (temperature)",
|
Title: "Tính ngẫu nhiên (temperature)",
|
||||||
|
@@ -35,7 +35,7 @@ export const EN_MASKS: BuiltinMask[] = [
|
|||||||
id: "prompt-improve-0",
|
id: "prompt-improve-0",
|
||||||
role: "user",
|
role: "user",
|
||||||
content:
|
content:
|
||||||
'Read all of the instructions below and once you understand them say "Shall we begin:"\n \nI want you to become my Prompt Creator. Your goal is to help me craft the best possible prompt for my needs. The prompt will be used by you, ChatGPT. You will follow the following process:\nYour first response will be to ask me what the prompt should be about. I will provide my answer, but we will need to improve it through continual iterations by going through the next steps.\n \nBased on my input, you will generate 3 sections.\n \nRevised Prompt (provide your rewritten prompt. it should be clear, concise, and easily understood by you)\nSuggestions (provide 3 suggestions on what details to include in the prompt to improve it)\nQuestions (ask the 3 most relevant questions pertaining to what additional information is needed from me to improve the prompt)\n \nAt the end of these sections give me a reminder of my options which are:\n \nOption 1: Read the output and provide more info or answer one or more of the questions\nOption 2: Type "Use this prompt" and I will submit this as a query for you\nOption 3: Type "Restart" to restart this process from the beginning\nOption 4: Type "Quit" to end this script and go back to a regular ChatGPT session\n \nIf I type "Option 2", "2" or "Use this prompt" then we have finsihed and you should use the Revised Prompt as a prompt to generate my request\nIf I type "option 3", "3" or "Restart" then forget the latest Revised Prompt and restart this process\nIf I type "Option 4", "4" or "Quit" then finish this process and revert back to your general mode of operation\n\n\nWe will continue this iterative process with me providing additional information to you and you updating the prompt in the Revised Prompt section until it is complete.',
|
'Read all of the instructions below and once you understand them say "Shall we begin:"\n \nI want you to become my Prompt Creator. Your goal is to help me craft the best possible prompt for my needs. The prompt will be used by you, ChatGPT. You will follow the following process:\nYour first response will be to ask me what the prompt should be about. I will provide my answer, but we will need to improve it through continual iterations by going through the next steps.\n \nBased on my input, you will generate 3 sections.\n \nRevised Prompt (provide your rewritten prompt. it should be clear, concise, and easily understood by you)\nSuggestions (provide 3 suggestions on what details to include in the prompt to improve it)\nQuestions (ask the 3 most relevant questions pertaining to what additional information is needed from me to improve the prompt)\n \nAt the end of these sections give me a reminder of my options which are:\n \nOption 1: Read the output and provide more info or answer one or more of the questions\nOption 2: Type "Use this prompt" and I will submit this as a query for you\nOption 3: Type "Restart" to restart this process from the beginning\nOption 4: Type "Quit" to end this script and go back to a regular ChatGPT session\n \nIf I type "Option 2", "2" or "Use this prompt" then we have finished and you should use the Revised Prompt as a prompt to generate my request\nIf I type "option 3", "3" or "Restart" then forget the latest Revised Prompt and restart this process\nIf I type "Option 4", "4" or "Quit" then finish this process and revert back to your general mode of operation\n\n\nWe will continue this iterative process with me providing additional information to you and you updating the prompt in the Revised Prompt section until it is complete.',
|
||||||
date: "",
|
date: "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@@ -1,23 +1,41 @@
|
|||||||
import { DEFAULT_API_HOST, DEFAULT_MODELS, StoreKey } from "../constant";
|
import {
|
||||||
|
ApiPath,
|
||||||
|
DEFAULT_API_HOST,
|
||||||
|
ServiceProvider,
|
||||||
|
StoreKey,
|
||||||
|
} from "../constant";
|
||||||
import { getHeaders } from "../client/api";
|
import { getHeaders } from "../client/api";
|
||||||
import { getClientConfig } from "../config/client";
|
import { getClientConfig } from "../config/client";
|
||||||
import { createPersistStore } from "../utils/store";
|
import { createPersistStore } from "../utils/store";
|
||||||
|
import { ensure } from "../utils/clone";
|
||||||
|
|
||||||
let fetchState = 0; // 0 not fetch, 1 fetching, 2 done
|
let fetchState = 0; // 0 not fetch, 1 fetching, 2 done
|
||||||
|
|
||||||
const DEFAULT_OPENAI_URL =
|
const DEFAULT_OPENAI_URL =
|
||||||
getClientConfig()?.buildMode === "export" ? DEFAULT_API_HOST : "/api/openai/";
|
getClientConfig()?.buildMode === "export" ? DEFAULT_API_HOST : ApiPath.OpenAI;
|
||||||
console.log("[API] default openai url", DEFAULT_OPENAI_URL);
|
|
||||||
|
|
||||||
const DEFAULT_ACCESS_STATE = {
|
const DEFAULT_ACCESS_STATE = {
|
||||||
token: "",
|
|
||||||
accessCode: "",
|
accessCode: "",
|
||||||
|
useCustomConfig: false,
|
||||||
|
|
||||||
|
provider: ServiceProvider.OpenAI,
|
||||||
|
|
||||||
|
// openai
|
||||||
|
openaiUrl: DEFAULT_OPENAI_URL,
|
||||||
|
openaiApiKey: "",
|
||||||
|
|
||||||
|
// azure
|
||||||
|
azureUrl: "",
|
||||||
|
azureApiKey: "",
|
||||||
|
azureApiVersion: "2023-08-01-preview",
|
||||||
|
|
||||||
|
// server config
|
||||||
needCode: true,
|
needCode: true,
|
||||||
hideUserApiKey: false,
|
hideUserApiKey: false,
|
||||||
hideBalanceQuery: false,
|
hideBalanceQuery: false,
|
||||||
disableGPT4: false,
|
disableGPT4: false,
|
||||||
|
disableFastLink: false,
|
||||||
openaiUrl: DEFAULT_OPENAI_URL,
|
customModels: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useAccessStore = createPersistStore(
|
export const useAccessStore = createPersistStore(
|
||||||
@@ -29,21 +47,24 @@ export const useAccessStore = createPersistStore(
|
|||||||
|
|
||||||
return get().needCode;
|
return get().needCode;
|
||||||
},
|
},
|
||||||
updateCode(code: string) {
|
|
||||||
set(() => ({ accessCode: code?.trim() }));
|
isValidOpenAI() {
|
||||||
|
return ensure(get(), ["openaiApiKey"]);
|
||||||
},
|
},
|
||||||
updateToken(token: string) {
|
|
||||||
set(() => ({ token: token?.trim() }));
|
isValidAzure() {
|
||||||
},
|
return ensure(get(), ["azureUrl", "azureApiKey", "azureApiVersion"]);
|
||||||
updateOpenAiUrl(url: string) {
|
|
||||||
set(() => ({ openaiUrl: url?.trim() }));
|
|
||||||
},
|
},
|
||||||
|
|
||||||
isAuthorized() {
|
isAuthorized() {
|
||||||
this.fetch();
|
this.fetch();
|
||||||
|
|
||||||
// has token or has code or disabled access control
|
// has token or has code or disabled access control
|
||||||
return (
|
return (
|
||||||
!!get().token || !!get().accessCode || !this.enabledAccessControl()
|
this.isValidOpenAI() ||
|
||||||
|
this.isValidAzure() ||
|
||||||
|
!this.enabledAccessControl() ||
|
||||||
|
(this.enabledAccessControl() && ensure(get(), ["accessCode"]))
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
fetch() {
|
fetch() {
|
||||||
@@ -60,12 +81,6 @@ export const useAccessStore = createPersistStore(
|
|||||||
.then((res: DangerConfig) => {
|
.then((res: DangerConfig) => {
|
||||||
console.log("[Config] got config from server", res);
|
console.log("[Config] got config from server", res);
|
||||||
set(() => ({ ...res }));
|
set(() => ({ ...res }));
|
||||||
|
|
||||||
if (res.disableGPT4) {
|
|
||||||
DEFAULT_MODELS.forEach(
|
|
||||||
(m: any) => (m.available = !m.name.startsWith("gpt-4")),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
console.error("[Config] failed to fetch config");
|
console.error("[Config] failed to fetch config");
|
||||||
@@ -77,6 +92,19 @@ export const useAccessStore = createPersistStore(
|
|||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
name: StoreKey.Access,
|
name: StoreKey.Access,
|
||||||
version: 1,
|
version: 2,
|
||||||
|
migrate(persistedState, version) {
|
||||||
|
if (version < 2) {
|
||||||
|
const state = persistedState as {
|
||||||
|
token: string;
|
||||||
|
openaiApiKey: string;
|
||||||
|
azureApiVersion: string;
|
||||||
|
};
|
||||||
|
state.openaiApiKey = state.token;
|
||||||
|
state.azureApiVersion = "2023-08-01-preview";
|
||||||
|
}
|
||||||
|
|
||||||
|
return persistedState as any;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@@ -1,6 +1,3 @@
|
|||||||
import { create } from "zustand";
|
|
||||||
import { persist } from "zustand/middleware";
|
|
||||||
|
|
||||||
import { trimTopic } from "../utils";
|
import { trimTopic } from "../utils";
|
||||||
|
|
||||||
import Locale, { getLang } from "../locales";
|
import Locale, { getLang } from "../locales";
|
||||||
@@ -10,6 +7,7 @@ import { createEmptyMask, Mask } from "./mask";
|
|||||||
import {
|
import {
|
||||||
DEFAULT_INPUT_TEMPLATE,
|
DEFAULT_INPUT_TEMPLATE,
|
||||||
DEFAULT_SYSTEM_TEMPLATE,
|
DEFAULT_SYSTEM_TEMPLATE,
|
||||||
|
KnowledgeCutOffDate,
|
||||||
StoreKey,
|
StoreKey,
|
||||||
SUMMARIZE_MODEL,
|
SUMMARIZE_MODEL,
|
||||||
} from "../constant";
|
} from "../constant";
|
||||||
@@ -87,39 +85,16 @@ function getSummarizeModel(currentModel: string) {
|
|||||||
return currentModel.startsWith("gpt") ? SUMMARIZE_MODEL : currentModel;
|
return currentModel.startsWith("gpt") ? SUMMARIZE_MODEL : currentModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ChatStore {
|
|
||||||
sessions: ChatSession[];
|
|
||||||
currentSessionIndex: number;
|
|
||||||
clearSessions: () => void;
|
|
||||||
moveSession: (from: number, to: number) => void;
|
|
||||||
selectSession: (index: number) => void;
|
|
||||||
newSession: (mask?: Mask) => void;
|
|
||||||
deleteSession: (index: number) => void;
|
|
||||||
currentSession: () => ChatSession;
|
|
||||||
nextSession: (delta: number) => void;
|
|
||||||
onNewMessage: (message: ChatMessage) => void;
|
|
||||||
onUserInput: (content: string) => Promise<void>;
|
|
||||||
summarizeSession: () => void;
|
|
||||||
updateStat: (message: ChatMessage) => void;
|
|
||||||
updateCurrentSession: (updater: (session: ChatSession) => void) => void;
|
|
||||||
updateMessage: (
|
|
||||||
sessionIndex: number,
|
|
||||||
messageIndex: number,
|
|
||||||
updater: (message?: ChatMessage) => void,
|
|
||||||
) => void;
|
|
||||||
resetSession: () => void;
|
|
||||||
getMessagesWithMemory: () => ChatMessage[];
|
|
||||||
getMemoryPrompt: () => ChatMessage;
|
|
||||||
|
|
||||||
clearAllData: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
function countMessages(msgs: ChatMessage[]) {
|
function countMessages(msgs: ChatMessage[]) {
|
||||||
return msgs.reduce((pre, cur) => pre + estimateTokenLength(cur.content), 0);
|
return msgs.reduce((pre, cur) => pre + estimateTokenLength(cur.content), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
function fillTemplateWith(input: string, modelConfig: ModelConfig) {
|
function fillTemplateWith(input: string, modelConfig: ModelConfig) {
|
||||||
|
let cutoff =
|
||||||
|
KnowledgeCutOffDate[modelConfig.model] ?? KnowledgeCutOffDate.default;
|
||||||
|
|
||||||
const vars = {
|
const vars = {
|
||||||
|
cutoff,
|
||||||
model: modelConfig.model,
|
model: modelConfig.model,
|
||||||
time: new Date().toLocaleString(),
|
time: new Date().toLocaleString(),
|
||||||
lang: getLang(),
|
lang: getLang(),
|
||||||
@@ -582,7 +557,10 @@ export const useChatStore = createPersistStore(
|
|||||||
},
|
},
|
||||||
onFinish(message) {
|
onFinish(message) {
|
||||||
console.log("[Memory] ", message);
|
console.log("[Memory] ", message);
|
||||||
session.lastSummarizeIndex = lastSummarizeIndex;
|
get().updateCurrentSession((session) => {
|
||||||
|
session.lastSummarizeIndex = lastSummarizeIndex;
|
||||||
|
session.memoryPrompt = message; // Update the memory prompt for stored it in local storage
|
||||||
|
});
|
||||||
},
|
},
|
||||||
onError(err) {
|
onError(err) {
|
||||||
console.error("[Summarize] ", err);
|
console.error("[Summarize] ", err);
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
import { LLMModel } from "../client/api";
|
import { LLMModel } from "../client/api";
|
||||||
|
import { isMacOS } from "../utils";
|
||||||
import { getClientConfig } from "../config/client";
|
import { getClientConfig } from "../config/client";
|
||||||
import {
|
import {
|
||||||
DEFAULT_INPUT_TEMPLATE,
|
DEFAULT_INPUT_TEMPLATE,
|
||||||
@@ -27,7 +28,7 @@ export enum Theme {
|
|||||||
export const DEFAULT_CONFIG = {
|
export const DEFAULT_CONFIG = {
|
||||||
lastUpdate: Date.now(), // timestamp, to merge state
|
lastUpdate: Date.now(), // timestamp, to merge state
|
||||||
|
|
||||||
submitKey: SubmitKey.CtrlEnter as SubmitKey,
|
submitKey: isMacOS() ? SubmitKey.MetaEnter : SubmitKey.CtrlEnter,
|
||||||
avatar: "1f603",
|
avatar: "1f603",
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
theme: Theme.Auto as Theme,
|
theme: Theme.Auto as Theme,
|
||||||
@@ -48,7 +49,7 @@ export const DEFAULT_CONFIG = {
|
|||||||
model: "gpt-3.5-turbo" as ModelType,
|
model: "gpt-3.5-turbo" as ModelType,
|
||||||
temperature: 0.5,
|
temperature: 0.5,
|
||||||
top_p: 1,
|
top_p: 1,
|
||||||
max_tokens: 2000,
|
max_tokens: 4000,
|
||||||
presence_penalty: 0,
|
presence_penalty: 0,
|
||||||
frequency_penalty: 0,
|
frequency_penalty: 0,
|
||||||
sendMemory: true,
|
sendMemory: true,
|
||||||
@@ -69,7 +70,7 @@ export function limitNumber(
|
|||||||
max: number,
|
max: number,
|
||||||
defaultValue: number,
|
defaultValue: number,
|
||||||
) {
|
) {
|
||||||
if (typeof x !== "number" || isNaN(x)) {
|
if (isNaN(x)) {
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,7 +82,7 @@ export const ModalConfigValidator = {
|
|||||||
return x as ModelType;
|
return x as ModelType;
|
||||||
},
|
},
|
||||||
max_tokens(x: number) {
|
max_tokens(x: number) {
|
||||||
return limitNumber(x, 0, 100000, 2000);
|
return limitNumber(x, 0, 512000, 1024);
|
||||||
},
|
},
|
||||||
presence_penalty(x: number) {
|
presence_penalty(x: number) {
|
||||||
return limitNumber(x, -2, 2, 0);
|
return limitNumber(x, -2, 2, 0);
|
||||||
@@ -127,15 +128,7 @@ export const useAppConfig = createPersistStore(
|
|||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
allModels() {
|
allModels() {},
|
||||||
const customModels = get()
|
|
||||||
.customModels.split(",")
|
|
||||||
.filter((v) => !!v && v.length > 0)
|
|
||||||
.map((m) => ({ name: m, available: true }));
|
|
||||||
|
|
||||||
const models = get().models.concat(customModels);
|
|
||||||
return models;
|
|
||||||
},
|
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
name: StoreKey.Config,
|
name: StoreKey.Config,
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
import { getClientConfig } from "../config/client";
|
||||||
import { Updater } from "../typing";
|
import { Updater } from "../typing";
|
||||||
import { ApiPath, STORAGE_KEY, StoreKey } from "../constant";
|
import { ApiPath, STORAGE_KEY, StoreKey } from "../constant";
|
||||||
import { createPersistStore } from "../utils/store";
|
import { createPersistStore } from "../utils/store";
|
||||||
@@ -20,6 +21,7 @@ export interface WebDavConfig {
|
|||||||
password: string;
|
password: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isApp = !!getClientConfig()?.isApp;
|
||||||
export type SyncStore = GetStoreState<typeof useSyncStore>;
|
export type SyncStore = GetStoreState<typeof useSyncStore>;
|
||||||
|
|
||||||
const DEFAULT_SYNC_STATE = {
|
const DEFAULT_SYNC_STATE = {
|
||||||
@@ -57,7 +59,11 @@ export const useSyncStore = createPersistStore(
|
|||||||
|
|
||||||
export() {
|
export() {
|
||||||
const state = getLocalAppState();
|
const state = getLocalAppState();
|
||||||
const fileName = `Backup-${new Date().toLocaleString()}.json`;
|
const datePart = isApp
|
||||||
|
? `${new Date().toLocaleDateString().replace(/\//g, '_')} ${new Date().toLocaleTimeString().replace(/:/g, '_')}`
|
||||||
|
: new Date().toLocaleString();
|
||||||
|
|
||||||
|
const fileName = `Backup-${datePart}.json`;
|
||||||
downloadAs(JSON.stringify(state), fileName);
|
downloadAs(JSON.stringify(state), fileName);
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -95,7 +101,7 @@ export const useSyncStore = createPersistStore(
|
|||||||
mergeAppState(localState, remoteState);
|
mergeAppState(localState, remoteState);
|
||||||
setLocalAppState(localState);
|
setLocalAppState(localState);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log("[Sync] failed to get remoate state", e);
|
console.log("[Sync] failed to get remote state", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
await client.set(config.username, JSON.stringify(localState));
|
await client.set(config.username, JSON.stringify(localState));
|
||||||
|
@@ -2,8 +2,11 @@ import { FETCH_COMMIT_URL, FETCH_TAG_URL, StoreKey } from "../constant";
|
|||||||
import { api } from "../client/api";
|
import { api } from "../client/api";
|
||||||
import { getClientConfig } from "../config/client";
|
import { getClientConfig } from "../config/client";
|
||||||
import { createPersistStore } from "../utils/store";
|
import { createPersistStore } from "../utils/store";
|
||||||
|
import ChatGptIcon from "../icons/chatgpt.png";
|
||||||
|
import Locale from "../locales";
|
||||||
|
|
||||||
const ONE_MINUTE = 60 * 1000;
|
const ONE_MINUTE = 60 * 1000;
|
||||||
|
const isApp = !!getClientConfig()?.isApp;
|
||||||
|
|
||||||
function formatVersionDate(t: string) {
|
function formatVersionDate(t: string) {
|
||||||
const d = new Date(+t);
|
const d = new Date(+t);
|
||||||
@@ -80,6 +83,38 @@ export const useUpdateStore = createPersistStore(
|
|||||||
set(() => ({
|
set(() => ({
|
||||||
remoteVersion: remoteId,
|
remoteVersion: remoteId,
|
||||||
}));
|
}));
|
||||||
|
if (window.__TAURI__?.notification && isApp) {
|
||||||
|
// Check if notification permission is granted
|
||||||
|
await window.__TAURI__?.notification.isPermissionGranted().then((granted) => {
|
||||||
|
if (!granted) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
// Request permission to show notifications
|
||||||
|
window.__TAURI__?.notification.requestPermission().then((permission) => {
|
||||||
|
if (permission === 'granted') {
|
||||||
|
if (version === remoteId) {
|
||||||
|
// Show a notification using Tauri
|
||||||
|
window.__TAURI__?.notification.sendNotification({
|
||||||
|
title: "ChatGPT Next Web",
|
||||||
|
body: `${Locale.Settings.Update.IsLatest}`,
|
||||||
|
icon: `${ChatGptIcon.src}`,
|
||||||
|
sound: "Default"
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const updateMessage = Locale.Settings.Update.FoundUpdate(`${remoteId}`);
|
||||||
|
// Show a notification for the new version using Tauri
|
||||||
|
window.__TAURI__?.notification.sendNotification({
|
||||||
|
title: "ChatGPT Next Web",
|
||||||
|
body: updateMessage,
|
||||||
|
icon: `${ChatGptIcon.src}`,
|
||||||
|
sound: "Default"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
console.log("[Got Upstream] ", remoteId);
|
console.log("[Got Upstream] ", remoteId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("[Fetch Upstream Commit Id]", error);
|
console.error("[Fetch Upstream Commit Id]", error);
|
||||||
|
@@ -357,3 +357,7 @@ pre {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.copyable {
|
||||||
|
user-select: text;
|
||||||
|
}
|
||||||
|
60
app/utils.ts
@@ -3,7 +3,10 @@ import { showToast } from "./components/ui-lib";
|
|||||||
import Locale from "./locales";
|
import Locale from "./locales";
|
||||||
|
|
||||||
export function trimTopic(topic: string) {
|
export function trimTopic(topic: string) {
|
||||||
return topic.replace(/[,。!?”“"、,.!?]*$/, "");
|
// Fix an issue where double quotes still show in the Indonesian language
|
||||||
|
// This will remove the specified punctuation from the end of the string
|
||||||
|
// and also trim quotes from both the start and end if they exist.
|
||||||
|
return topic.replace(/^["“”]+|["“”]+$/g, "").replace(/[,。!?”“"、,.!?]*$/, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function copyToClipboard(text: string) {
|
export async function copyToClipboard(text: string) {
|
||||||
@@ -31,12 +34,41 @@ export async function copyToClipboard(text: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function downloadAs(text: string, filename: string) {
|
export async function downloadAs(text: string, filename: string) {
|
||||||
const element = document.createElement("a");
|
if (window.__TAURI__) {
|
||||||
element.setAttribute(
|
const result = await window.__TAURI__.dialog.save({
|
||||||
"href",
|
defaultPath: `${filename}`,
|
||||||
"data:text/plain;charset=utf-8," + encodeURIComponent(text),
|
filters: [
|
||||||
);
|
{
|
||||||
|
name: `${filename.split('.').pop()} files`,
|
||||||
|
extensions: [`${filename.split('.').pop()}`],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "All Files",
|
||||||
|
extensions: ["*"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result !== null) {
|
||||||
|
try {
|
||||||
|
await window.__TAURI__.fs.writeBinaryFile(
|
||||||
|
result,
|
||||||
|
new Uint8Array([...text].map((c) => c.charCodeAt(0)))
|
||||||
|
);
|
||||||
|
showToast(Locale.Download.Success);
|
||||||
|
} catch (error) {
|
||||||
|
showToast(Locale.Download.Failed);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
showToast(Locale.Download.Failed);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const element = document.createElement("a");
|
||||||
|
element.setAttribute(
|
||||||
|
"href",
|
||||||
|
"data:text/plain;charset=utf-8," + encodeURIComponent(text),
|
||||||
|
);
|
||||||
element.setAttribute("download", filename);
|
element.setAttribute("download", filename);
|
||||||
|
|
||||||
element.style.display = "none";
|
element.style.display = "none";
|
||||||
@@ -46,7 +78,7 @@ export function downloadAs(text: string, filename: string) {
|
|||||||
|
|
||||||
document.body.removeChild(element);
|
document.body.removeChild(element);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
export function readFromFile() {
|
export function readFromFile() {
|
||||||
return new Promise<string>((res, rej) => {
|
return new Promise<string>((res, rej) => {
|
||||||
const fileInput = document.createElement("input");
|
const fileInput = document.createElement("input");
|
||||||
@@ -173,3 +205,15 @@ export function autoGrowTextArea(dom: HTMLTextAreaElement) {
|
|||||||
export function getCSSVar(varName: string) {
|
export function getCSSVar(varName: string) {
|
||||||
return getComputedStyle(document.body).getPropertyValue(varName).trim();
|
return getComputedStyle(document.body).getPropertyValue(varName).trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detects Macintosh
|
||||||
|
*/
|
||||||
|
export function isMacOS(): boolean {
|
||||||
|
if (typeof window !== "undefined") {
|
||||||
|
let userAgent = window.navigator.userAgent.toLocaleLowerCase();
|
||||||
|
const macintosh = /iphone|ipad|ipod|macintosh/.test(userAgent)
|
||||||
|
return !!macintosh
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
@@ -1,3 +1,12 @@
|
|||||||
export function deepClone<T>(obj: T) {
|
export function deepClone<T>(obj: T) {
|
||||||
return JSON.parse(JSON.stringify(obj));
|
return JSON.parse(JSON.stringify(obj));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function ensure<T extends object>(
|
||||||
|
obj: T,
|
||||||
|
keys: Array<[keyof T][number]>,
|
||||||
|
) {
|
||||||
|
return keys.every(
|
||||||
|
(k) => obj[k] !== undefined && obj[k] !== null && obj[k] !== "",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@@ -21,7 +21,7 @@ export function createWebDavClient(store: SyncStore) {
|
|||||||
proxyUrl,
|
proxyUrl,
|
||||||
});
|
});
|
||||||
console.log("[WebDav] check", res.status, res.statusText);
|
console.log("[WebDav] check", res.status, res.statusText);
|
||||||
return [201, 200, 404, 401].includes(res.status);
|
return [201, 200, 404, 301, 302, 307, 308].includes(res.status);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("[WebDav] failed to check", e);
|
console.error("[WebDav] failed to check", e);
|
||||||
}
|
}
|
||||||
|
16
app/utils/hooks.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { useMemo } from "react";
|
||||||
|
import { useAccessStore, useAppConfig } from "../store";
|
||||||
|
import { collectModels } from "./model";
|
||||||
|
|
||||||
|
export function useAllModels() {
|
||||||
|
const accessStore = useAccessStore();
|
||||||
|
const configStore = useAppConfig();
|
||||||
|
const models = useMemo(() => {
|
||||||
|
return collectModels(
|
||||||
|
configStore.models,
|
||||||
|
[configStore.customModels, accessStore.customModels].join(","),
|
||||||
|
);
|
||||||
|
}, [accessStore.customModels, configStore.customModels, configStore.models]);
|
||||||
|
|
||||||
|
return models;
|
||||||
|
}
|
56
app/utils/model.ts
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import { LLMModel } from "../client/api";
|
||||||
|
|
||||||
|
export function collectModelTable(
|
||||||
|
models: readonly LLMModel[],
|
||||||
|
customModels: string,
|
||||||
|
) {
|
||||||
|
const modelTable: Record<
|
||||||
|
string,
|
||||||
|
{ available: boolean; name: string; displayName: string }
|
||||||
|
> = {};
|
||||||
|
|
||||||
|
// default models
|
||||||
|
models.forEach(
|
||||||
|
(m) =>
|
||||||
|
(modelTable[m.name] = {
|
||||||
|
...m,
|
||||||
|
displayName: m.name,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// server custom models
|
||||||
|
customModels
|
||||||
|
.split(",")
|
||||||
|
.filter((v) => !!v && v.length > 0)
|
||||||
|
.map((m) => {
|
||||||
|
const available = !m.startsWith("-");
|
||||||
|
const nameConfig =
|
||||||
|
m.startsWith("+") || m.startsWith("-") ? m.slice(1) : m;
|
||||||
|
const [name, displayName] = nameConfig.split("=");
|
||||||
|
|
||||||
|
// enable or disable all models
|
||||||
|
if (name === "all") {
|
||||||
|
Object.values(modelTable).forEach((m) => (m.available = available));
|
||||||
|
}
|
||||||
|
|
||||||
|
modelTable[name] = {
|
||||||
|
name,
|
||||||
|
displayName: displayName || name,
|
||||||
|
available,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return modelTable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate full model table.
|
||||||
|
*/
|
||||||
|
export function collectModels(
|
||||||
|
models: readonly LLMModel[],
|
||||||
|
customModels: string,
|
||||||
|
) {
|
||||||
|
const modelTable = collectModelTable(models, customModels);
|
||||||
|
const allModels = Object.values(modelTable);
|
||||||
|
|
||||||
|
return allModels;
|
||||||
|
}
|
@@ -1,5 +1,5 @@
|
|||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
import { persist } from "zustand/middleware";
|
import { combine, persist } from "zustand/middleware";
|
||||||
import { Updater } from "../typing";
|
import { Updater } from "../typing";
|
||||||
import { deepClone } from "./clone";
|
import { deepClone } from "./clone";
|
||||||
|
|
||||||
@@ -23,33 +23,42 @@ type SetStoreState<T> = (
|
|||||||
replace?: boolean | undefined,
|
replace?: boolean | undefined,
|
||||||
) => void;
|
) => void;
|
||||||
|
|
||||||
export function createPersistStore<T, M>(
|
export function createPersistStore<T extends object, M>(
|
||||||
defaultState: T,
|
state: T,
|
||||||
methods: (
|
methods: (
|
||||||
set: SetStoreState<T & MakeUpdater<T>>,
|
set: SetStoreState<T & MakeUpdater<T>>,
|
||||||
get: () => T & MakeUpdater<T>,
|
get: () => T & MakeUpdater<T>,
|
||||||
) => M,
|
) => M,
|
||||||
persistOptions: SecondParam<typeof persist<T & M & MakeUpdater<T>>>,
|
persistOptions: SecondParam<typeof persist<T & M & MakeUpdater<T>>>,
|
||||||
) {
|
) {
|
||||||
return create<T & M & MakeUpdater<T>>()(
|
return create(
|
||||||
persist((set, get) => {
|
persist(
|
||||||
return {
|
combine(
|
||||||
...defaultState,
|
{
|
||||||
...methods(set as any, get),
|
...state,
|
||||||
|
lastUpdateTime: 0,
|
||||||
|
},
|
||||||
|
(set, get) => {
|
||||||
|
return {
|
||||||
|
...methods(set, get as any),
|
||||||
|
|
||||||
lastUpdateTime: 0,
|
markUpdate() {
|
||||||
markUpdate() {
|
set({ lastUpdateTime: Date.now() } as Partial<
|
||||||
set({ lastUpdateTime: Date.now() } as Partial<
|
T & M & MakeUpdater<T>
|
||||||
T & M & MakeUpdater<T>
|
>);
|
||||||
>);
|
},
|
||||||
|
update(updater) {
|
||||||
|
const state = deepClone(get());
|
||||||
|
updater(state);
|
||||||
|
set({
|
||||||
|
...state,
|
||||||
|
lastUpdateTime: Date.now(),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
} as M & MakeUpdater<T>;
|
||||||
},
|
},
|
||||||
update(updater) {
|
),
|
||||||
const state = deepClone(get());
|
persistOptions as any,
|
||||||
updater(state);
|
),
|
||||||
get().markUpdate();
|
|
||||||
set(state);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}, persistOptions),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
version: '3.9'
|
version: "3.9"
|
||||||
services:
|
services:
|
||||||
chatgpt-next-web:
|
chatgpt-next-web:
|
||||||
profiles: ["no-proxy"]
|
profiles: ["no-proxy"]
|
||||||
container_name: chatgpt-next-web
|
container_name: chatgpt-next-web
|
||||||
image: yidadaa/chatgpt-next-web
|
image: yidadaa/chatgpt-next-web
|
||||||
@@ -13,8 +13,11 @@ services:
|
|||||||
- OPENAI_ORG_ID=$OPENAI_ORG_ID
|
- OPENAI_ORG_ID=$OPENAI_ORG_ID
|
||||||
- HIDE_USER_API_KEY=$HIDE_USER_API_KEY
|
- HIDE_USER_API_KEY=$HIDE_USER_API_KEY
|
||||||
- DISABLE_GPT4=$DISABLE_GPT4
|
- DISABLE_GPT4=$DISABLE_GPT4
|
||||||
|
- ENABLE_BALANCE_QUERY=$ENABLE_BALANCE_QUERY
|
||||||
|
- DISABLE_FAST_LINK=$DISABLE_FAST_LINK
|
||||||
|
- OPENAI_SB=$OPENAI_SB
|
||||||
|
|
||||||
chatgpt-next-web-proxy:
|
chatgpt-next-web-proxy:
|
||||||
profiles: ["proxy"]
|
profiles: ["proxy"]
|
||||||
container_name: chatgpt-next-web-proxy
|
container_name: chatgpt-next-web-proxy
|
||||||
image: yidadaa/chatgpt-next-web
|
image: yidadaa/chatgpt-next-web
|
||||||
@@ -28,3 +31,6 @@ services:
|
|||||||
- OPENAI_ORG_ID=$OPENAI_ORG_ID
|
- OPENAI_ORG_ID=$OPENAI_ORG_ID
|
||||||
- HIDE_USER_API_KEY=$HIDE_USER_API_KEY
|
- HIDE_USER_API_KEY=$HIDE_USER_API_KEY
|
||||||
- DISABLE_GPT4=$DISABLE_GPT4
|
- DISABLE_GPT4=$DISABLE_GPT4
|
||||||
|
- ENABLE_BALANCE_QUERY=$ENABLE_BALANCE_QUERY
|
||||||
|
- DISABLE_FAST_LINK=$DISABLE_FAST_LINK
|
||||||
|
- OPENAI_SB=$OPENAI_SB
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
# Cloudflare Pages 部署指南
|
# Cloudflare Pages 部署指南
|
||||||
|
|
||||||
## 如何新建项目
|
## 如何新建项目
|
||||||
|
|
||||||
在 Github 上 fork 本项目,然后登录到 dash.cloudflare.com 并进入 Pages。
|
在 Github 上 fork 本项目,然后登录到 dash.cloudflare.com 并进入 Pages。
|
||||||
|
|
||||||
1. 点击 "Create a project"。
|
1. 点击 "Create a project"。
|
||||||
@@ -12,7 +13,7 @@
|
|||||||
7. 在 "Build Settings" 中,选择 "Framework presets" 选项并选择 "Next.js"。
|
7. 在 "Build Settings" 中,选择 "Framework presets" 选项并选择 "Next.js"。
|
||||||
8. 由于 node:buffer 的 bug,暂时不要使用默认的 "Build command"。请使用以下命令:
|
8. 由于 node:buffer 的 bug,暂时不要使用默认的 "Build command"。请使用以下命令:
|
||||||
```
|
```
|
||||||
npx https://prerelease-registry.devprod.cloudflare.dev/next-on-pages/runs/4930842298/npm-package-next-on-pages-230 --experimental-minify
|
npx @cloudflare/next-on-pages@1.5.0
|
||||||
```
|
```
|
||||||
9. 对于 "Build output directory",使用默认值并且不要修改。
|
9. 对于 "Build output directory",使用默认值并且不要修改。
|
||||||
10. 不要修改 "Root Directory"。
|
10. 不要修改 "Root Directory"。
|
||||||
@@ -30,10 +31,12 @@
|
|||||||
- `OPENAI_ORG_ID= 可选填,指定 OpenAI 中的组织 ID`
|
- `OPENAI_ORG_ID= 可选填,指定 OpenAI 中的组织 ID`
|
||||||
- `HIDE_USER_API_KEY=1 可选,不让用户自行填入 API Key`
|
- `HIDE_USER_API_KEY=1 可选,不让用户自行填入 API Key`
|
||||||
- `DISABLE_GPT4=1 可选,不让用户使用 GPT-4`
|
- `DISABLE_GPT4=1 可选,不让用户使用 GPT-4`
|
||||||
|
- `ENABLE_BALANCE_QUERY=1 可选,启用余额查询功能`
|
||||||
|
- `DISABLE_FAST_LINK=1 可选,禁用从链接解析预制设置`
|
||||||
|
|
||||||
12. 点击 "Save and Deploy"。
|
12. 点击 "Save and Deploy"。
|
||||||
13. 点击 "Cancel deployment",因为需要填写 Compatibility flags。
|
13. 点击 "Cancel deployment",因为需要填写 Compatibility flags。
|
||||||
14. 前往 "Build settings"、"Functions",找到 "Compatibility flags"。
|
14. 前往 "Build settings"、"Functions",找到 "Compatibility flags"。
|
||||||
15. 在 "Configure Production compatibility flag" 和 "Configure Preview compatibility flag" 中填写 "nodejs_compat"。
|
15. 在 "Configure Production compatibility flag" 和 "Configure Preview compatibility flag" 中填写 "nodejs_compat"。
|
||||||
16. 前往 "Deployments",点击 "Retry deployment"。
|
16. 前往 "Deployments",点击 "Retry deployment"。
|
||||||
17. Enjoy.
|
17. Enjoy.
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
# Cloudflare Pages Deployment Guide
|
# Cloudflare Pages Deployment Guide
|
||||||
|
|
||||||
## How to create a new project
|
## How to create a new project
|
||||||
|
|
||||||
Fork this project on GitHub, then log in to dash.cloudflare.com and go to Pages.
|
Fork this project on GitHub, then log in to dash.cloudflare.com and go to Pages.
|
||||||
|
|
||||||
1. Click "Create a project".
|
1. Click "Create a project".
|
||||||
@@ -11,12 +12,13 @@ Fork this project on GitHub, then log in to dash.cloudflare.com and go to Pages.
|
|||||||
6. For "Project name" and "Production branch", use the default values or change them as needed.
|
6. For "Project name" and "Production branch", use the default values or change them as needed.
|
||||||
7. In "Build Settings", choose the "Framework presets" option and select "Next.js".
|
7. In "Build Settings", choose the "Framework presets" option and select "Next.js".
|
||||||
8. Do not use the default "Build command" due to a node:buffer bug. Instead, use the following command:
|
8. Do not use the default "Build command" due to a node:buffer bug. Instead, use the following command:
|
||||||
```
|
```
|
||||||
npx @cloudflare/next-on-pages --experimental-minify
|
npx @cloudflare/next-on-pages --experimental-minify
|
||||||
```
|
```
|
||||||
9. For "Build output directory", use the default value and do not modify it.
|
9. For "Build output directory", use the default value and do not modify it.
|
||||||
10. Do not modify "Root Directory".
|
10. Do not modify "Root Directory".
|
||||||
11. For "Environment variables", click ">" and then "Add variable". Fill in the following information:
|
11. For "Environment variables", click ">" and then "Add variable". Fill in the following information:
|
||||||
|
|
||||||
- `NODE_VERSION=20.1`
|
- `NODE_VERSION=20.1`
|
||||||
- `NEXT_TELEMETRY_DISABLE=1`
|
- `NEXT_TELEMETRY_DISABLE=1`
|
||||||
- `OPENAI_API_KEY=your_own_API_key`
|
- `OPENAI_API_KEY=your_own_API_key`
|
||||||
@@ -29,7 +31,10 @@ Fork this project on GitHub, then log in to dash.cloudflare.com and go to Pages.
|
|||||||
- `OPENAI_ORG_ID= Optional, specify the organization ID in OpenAI`
|
- `OPENAI_ORG_ID= Optional, specify the organization ID in OpenAI`
|
||||||
- `HIDE_USER_API_KEY=1 Optional, do not allow users to enter their own API key`
|
- `HIDE_USER_API_KEY=1 Optional, do not allow users to enter their own API key`
|
||||||
- `DISABLE_GPT4=1 Optional, do not allow users to use GPT-4`
|
- `DISABLE_GPT4=1 Optional, do not allow users to use GPT-4`
|
||||||
|
- `ENABLE_BALANCE_QUERY=1 Optional, allow users to query balance`
|
||||||
|
- `DISABLE_FAST_LINK=1 Optional, disable parse settings from url`
|
||||||
|
- `OPENAI_SB=1 Optional,use the third-party OpenAI-SB API`
|
||||||
|
|
||||||
12. Click "Save and Deploy".
|
12. Click "Save and Deploy".
|
||||||
13. Click "Cancel deployment" because you need to fill in Compatibility flags.
|
13. Click "Cancel deployment" because you need to fill in Compatibility flags.
|
||||||
14. Go to "Build settings", "Functions", and find "Compatibility flags".
|
14. Go to "Build settings", "Functions", and find "Compatibility flags".
|
||||||
|
BIN
docs/images/upstash-1.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
docs/images/upstash-2.png
Normal file
After Width: | Height: | Size: 65 KiB |
BIN
docs/images/upstash-3.png
Normal file
After Width: | Height: | Size: 49 KiB |
BIN
docs/images/upstash-4.png
Normal file
After Width: | Height: | Size: 37 KiB |
BIN
docs/images/upstash-5.png
Normal file
After Width: | Height: | Size: 67 KiB |
BIN
docs/images/upstash-6.png
Normal file
After Width: | Height: | Size: 66 KiB |
BIN
docs/images/upstash-7.png
Normal file
After Width: | Height: | Size: 84 KiB |
31
docs/synchronise-chat-logs-cn.md
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# 同步聊天记录
|
||||||
|
## 准备工作
|
||||||
|
- GitHub账号
|
||||||
|
- 拥有自己搭建过的ChatGPT-Next-Web的服务器
|
||||||
|
- [UpStash](https://upstash.com)
|
||||||
|
|
||||||
|
## 开始教程
|
||||||
|
1. 注册UpStash账号
|
||||||
|
2. 创建数据库
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
3. 找到REST API,分别复制UPSTASH_REDIS_REST_URL和UPSTASH_REDIS_REST_TOKEN(⚠切记⚠:不要泄露Token!)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
4. UPSTASH_REDIS_REST_URL和UPSTASH_REDIS_REST_TOKEN复制到你的同步配置,点击**检查可用性**
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
如果没什么问题,那就成功了
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
5. Success!
|
||||||
|
|
||||||
|

|
31
docs/synchronise-chat-logs-en.md
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# Synchronize Chat Logs with UpStash
|
||||||
|
## Prerequisites
|
||||||
|
- GitHub account
|
||||||
|
- Your own ChatGPT-Next-Web server set up
|
||||||
|
- [UpStash](https://upstash.com)
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
1. Register for an UpStash account.
|
||||||
|
2. Create a database.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
3. Find the REST API and copy UPSTASH_REDIS_REST_URL and UPSTASH_REDIS_REST_TOKEN (⚠Important⚠: Do not share your token!)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
4. Copy UPSTASH_REDIS_REST_URL and UPSTASH_REDIS_REST_TOKEN into your synchronization configuration, then click **Check Availability**.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
If everything is in order, you've successfully completed this step.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
5. Success!
|
||||||
|
|
||||||
|

|
31
docs/synchronise-chat-logs-es.md
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# Sincronizzare i Log delle Chat con UpStash
|
||||||
|
## Prerequisiti
|
||||||
|
- Account GitHub
|
||||||
|
- Server ChatGPT-Next-Web di propria configurazione
|
||||||
|
- [UpStash](https://upstash.com)
|
||||||
|
|
||||||
|
## Per iniziare
|
||||||
|
1. Registrarsi per un account UpStash.
|
||||||
|
2. Creare un database.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
3. Trovare l'API REST e copiare UPSTASH_REDIS_REST_URL e UPSTASH_REDIS_REST_TOKEN (⚠Importante⚠: Non condividere il token!)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
4. Copiare UPSTASH_REDIS_REST_URL e UPSTASH_REDIS_REST_TOKEN nella configurazione di sincronizzazione, quindi fare clic su **Verifica la Disponibilità**.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Se tutto è in ordine, hai completato con successo questa fase.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
5. Successo!
|
||||||
|
|
||||||
|

|
31
docs/synchronise-chat-logs-ja.md
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# UpStashを使用してチャットログを同期する
|
||||||
|
## 事前準備
|
||||||
|
- GitHubアカウント
|
||||||
|
- 自分自身でChatGPT-Next-Webのサーバーをセットアップしていること
|
||||||
|
- [UpStash](https://upstash.com)
|
||||||
|
|
||||||
|
## 始める
|
||||||
|
1. UpStashアカウントを登録します。
|
||||||
|
2. データベースを作成します。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
3. REST APIを見つけ、UPSTASH_REDIS_REST_URLとUPSTASH_REDIS_REST_TOKENをコピーします(⚠重要⚠:トークンを共有しないでください!)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
4. UPSTASH_REDIS_REST_URLとUPSTASH_REDIS_REST_TOKENを同期設定にコピーし、次に「可用性を確認」をクリックします。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
すべてが正常であれば、このステップは成功です。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
5. 成功!
|
||||||
|
|
||||||
|

|
31
docs/synchronise-chat-logs-ko.md
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# UpStash를 사용하여 채팅 기록 동기화
|
||||||
|
## 사전 준비물
|
||||||
|
- GitHub 계정
|
||||||
|
- 자체 ChatGPT-Next-Web 서버 설정
|
||||||
|
- [UpStash](https://upstash.com)
|
||||||
|
|
||||||
|
## 시작하기
|
||||||
|
1. UpStash 계정 등록
|
||||||
|
2. 데이터베이스 생성
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
3. REST API를 찾아 UPSTASH_REDIS_REST_URL 및 UPSTASH_REDIS_REST_TOKEN을 복사합니다 (⚠주의⚠: 토큰을 공유하지 마십시오!)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
4. UPSTASH_REDIS_REST_URL 및 UPSTASH_REDIS_REST_TOKEN을 동기화 구성에 복사한 다음 **가용성 확인**을 클릭합니다.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
모든 것이 정상인 경우,이 단계를 성공적으로 완료했습니다.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
5. 성공!
|
||||||
|
|
||||||
|

|
10
package.json
@@ -20,11 +20,11 @@
|
|||||||
"@hello-pangea/dnd": "^16.3.0",
|
"@hello-pangea/dnd": "^16.3.0",
|
||||||
"@svgr/webpack": "^6.5.1",
|
"@svgr/webpack": "^6.5.1",
|
||||||
"@vercel/analytics": "^0.1.11",
|
"@vercel/analytics": "^0.1.11",
|
||||||
"emoji-picker-react": "^4.5.1",
|
"emoji-picker-react": "^4.5.15",
|
||||||
"fuse.js": "^6.6.2",
|
"fuse.js": "^6.6.2",
|
||||||
"html-to-image": "^1.11.11",
|
"html-to-image": "^1.11.11",
|
||||||
"mermaid": "^10.3.1",
|
"mermaid": "^10.6.1",
|
||||||
"nanoid": "^4.0.2",
|
"nanoid": "^5.0.3",
|
||||||
"next": "^13.4.9",
|
"next": "^13.4.9",
|
||||||
"node-fetch": "^3.3.1",
|
"node-fetch": "^3.3.1",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
@@ -43,11 +43,11 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tauri-apps/cli": "^1.4.0",
|
"@tauri-apps/cli": "^1.4.0",
|
||||||
"@types/node": "^20.3.3",
|
"@types/node": "^20.9.0",
|
||||||
"@types/react": "^18.2.14",
|
"@types/react": "^18.2.14",
|
||||||
"@types/react-dom": "^18.2.7",
|
"@types/react-dom": "^18.2.7",
|
||||||
"@types/react-katex": "^3.0.0",
|
"@types/react-katex": "^3.0.0",
|
||||||
"@types/spark-md5": "^3.0.2",
|
"@types/spark-md5": "^3.0.4",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"eslint": "^8.49.0",
|
"eslint": "^8.49.0",
|
||||||
"eslint-config-next": "13.4.19",
|
"eslint-config-next": "13.4.19",
|
||||||
|
@@ -17,7 +17,7 @@ tauri-build = { version = "1.3.0", features = [] }
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
tauri = { version = "1.3.0", features = ["clipboard-all", "dialog-all", "shell-open", "updater", "window-close", "window-hide", "window-maximize", "window-minimize", "window-set-icon", "window-set-ignore-cursor-events", "window-set-resizable", "window-show", "window-start-dragging", "window-unmaximize", "window-unminimize"] }
|
tauri = { version = "1.3.0", features = ["notification-all", "fs-all", "clipboard-all", "dialog-all", "shell-open", "updater", "window-close", "window-hide", "window-maximize", "window-minimize", "window-set-icon", "window-set-ignore-cursor-events", "window-set-resizable", "window-show", "window-start-dragging", "window-unmaximize", "window-unminimize"] }
|
||||||
tauri-plugin-window-state = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
|
tauri-plugin-window-state = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
@@ -9,7 +9,7 @@
|
|||||||
},
|
},
|
||||||
"package": {
|
"package": {
|
||||||
"productName": "ChatGPT Next Web",
|
"productName": "ChatGPT Next Web",
|
||||||
"version": "2.9.7"
|
"version": "2.9.13"
|
||||||
},
|
},
|
||||||
"tauri": {
|
"tauri": {
|
||||||
"allowlist": {
|
"allowlist": {
|
||||||
@@ -44,6 +44,12 @@
|
|||||||
"startDragging": true,
|
"startDragging": true,
|
||||||
"unmaximize": true,
|
"unmaximize": true,
|
||||||
"unminimize": true
|
"unminimize": true
|
||||||
|
},
|
||||||
|
"fs": {
|
||||||
|
"all": true
|
||||||
|
},
|
||||||
|
"notification": {
|
||||||
|
"all": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"bundle": {
|
"bundle": {
|
||||||
|
21
vercel.json
@@ -1,24 +1,5 @@
|
|||||||
{
|
{
|
||||||
"github": {
|
"github": {
|
||||||
"silent": true
|
"silent": true
|
||||||
},
|
}
|
||||||
"headers": [
|
|
||||||
{
|
|
||||||
"source": "/(.*)",
|
|
||||||
"headers": [
|
|
||||||
{
|
|
||||||
"key": "X-Real-IP",
|
|
||||||
"value": "$remote_addr"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "X-Forwarded-For",
|
|
||||||
"value": "$proxy_add_x_forwarded_for"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "Host",
|
|
||||||
"value": "$http_host"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
54
yarn.lock
@@ -1507,10 +1507,12 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.31.tgz#31b7ca6407128a3d2bbc27fe2d21b345397f6197"
|
resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.31.tgz#31b7ca6407128a3d2bbc27fe2d21b345397f6197"
|
||||||
integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==
|
integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==
|
||||||
|
|
||||||
"@types/node@*", "@types/node@^20.3.3":
|
"@types/node@*", "@types/node@^20.9.0":
|
||||||
version "20.3.3"
|
version "20.9.0"
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.3.3.tgz#329842940042d2b280897150e023e604d11657d6"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.9.0.tgz#bfcdc230583aeb891cf51e73cfdaacdd8deae298"
|
||||||
integrity sha512-wheIYdr4NYML61AjC8MKj/2jrR/kDQri/CIpVoZwldwhnIrD/j9jIU5bJ8yBKuB2VhpFV7Ab6G2XkBjv9r9Zzw==
|
integrity sha512-nekiGu2NDb1BcVofVcEKMIwzlx4NjHlcjhoxxKBNLtz15Y1z7MYf549DFvkHSId02Ax6kGwWntIBPC3l/JZcmw==
|
||||||
|
dependencies:
|
||||||
|
undici-types "~5.26.4"
|
||||||
|
|
||||||
"@types/parse-json@^4.0.0":
|
"@types/parse-json@^4.0.0":
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
@@ -1550,10 +1552,10 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.3.tgz#cef09e3ec9af1d63d2a6cc5b383a737e24e6dcf5"
|
resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.3.tgz#cef09e3ec9af1d63d2a6cc5b383a737e24e6dcf5"
|
||||||
integrity sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==
|
integrity sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==
|
||||||
|
|
||||||
"@types/spark-md5@^3.0.2":
|
"@types/spark-md5@^3.0.4":
|
||||||
version "3.0.2"
|
version "3.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/@types/spark-md5/-/spark-md5-3.0.2.tgz#da2e8a778a20335fc4f40b6471c4b0d86b70da55"
|
resolved "https://registry.yarnpkg.com/@types/spark-md5/-/spark-md5-3.0.4.tgz#c1221d63c069d95aba0c06a765b80661cacc12bf"
|
||||||
integrity sha512-82E/lVRaqelV9qmRzzJ1PKTpyrpnT7mwdneKNJB9hUtypZDMggloDfFUCIqRRx3lYRxteCwXSq9c+W71Vf0QnQ==
|
integrity sha512-qtOaDz+IXiNndPgYb6t1YoutnGvFRtWSNzpVjkAPCfB2UzTyybuD4Tjgs7VgRawum3JnJNRwNQd4N//SvrHg1Q==
|
||||||
|
|
||||||
"@types/unist@*", "@types/unist@^2.0.0":
|
"@types/unist@*", "@types/unist@^2.0.0":
|
||||||
version "2.0.6"
|
version "2.0.6"
|
||||||
@@ -2126,11 +2128,6 @@ client-only@0.0.1:
|
|||||||
resolved "https://registry.yarnpkg.com/client-only/-/client-only-0.0.1.tgz#38bba5d403c41ab150bff64a95c85013cf73bca1"
|
resolved "https://registry.yarnpkg.com/client-only/-/client-only-0.0.1.tgz#38bba5d403c41ab150bff64a95c85013cf73bca1"
|
||||||
integrity sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==
|
integrity sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==
|
||||||
|
|
||||||
clsx@^1.2.1:
|
|
||||||
version "1.2.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12"
|
|
||||||
integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==
|
|
||||||
|
|
||||||
color-convert@^1.9.0:
|
color-convert@^1.9.0:
|
||||||
version "1.9.3"
|
version "1.9.3"
|
||||||
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
|
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
|
||||||
@@ -2762,12 +2759,10 @@ elkjs@^0.8.2:
|
|||||||
resolved "https://registry.npmmirror.com/elkjs/-/elkjs-0.8.2.tgz#c37763c5a3e24e042e318455e0147c912a7c248e"
|
resolved "https://registry.npmmirror.com/elkjs/-/elkjs-0.8.2.tgz#c37763c5a3e24e042e318455e0147c912a7c248e"
|
||||||
integrity sha512-L6uRgvZTH+4OF5NE/MBbzQx/WYpru1xCBE9respNj6qznEewGUIfhzmm7horWWxbNO2M0WckQypGctR8lH79xQ==
|
integrity sha512-L6uRgvZTH+4OF5NE/MBbzQx/WYpru1xCBE9respNj6qznEewGUIfhzmm7horWWxbNO2M0WckQypGctR8lH79xQ==
|
||||||
|
|
||||||
emoji-picker-react@^4.5.1:
|
emoji-picker-react@^4.5.15:
|
||||||
version "4.5.1"
|
version "4.5.15"
|
||||||
resolved "https://registry.yarnpkg.com/emoji-picker-react/-/emoji-picker-react-4.5.1.tgz#341f27dc86ad09340a316e0632484fcb9aff7195"
|
resolved "https://registry.yarnpkg.com/emoji-picker-react/-/emoji-picker-react-4.5.15.tgz#e12797c50584cb8af8aee7eb6c7c8fd953e41f7e"
|
||||||
integrity sha512-zpm0ui0TWkXZDUIevyNM0rC9Jyqc08RvVXH0KgsbSkDr+VgMQmYLu6UeI4SIWMZKsKMjQwujPpncRCFlEeykjw==
|
integrity sha512-BTqo+pNUE8kqX8BKFTbD4fhlxcA69qfie5En4PerReLaaPfXVyRlDJ1uf85nKj2u5esUQ999iUf8YyqcPsM2Qw==
|
||||||
dependencies:
|
|
||||||
clsx "^1.2.1"
|
|
||||||
|
|
||||||
emoji-regex@^8.0.0:
|
emoji-regex@^8.0.0:
|
||||||
version "8.0.0"
|
version "8.0.0"
|
||||||
@@ -4314,10 +4309,10 @@ merge2@^1.3.0, merge2@^1.4.1:
|
|||||||
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
|
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
|
||||||
integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
|
integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
|
||||||
|
|
||||||
mermaid@^10.3.1:
|
mermaid@^10.6.1:
|
||||||
version "10.3.1"
|
version "10.6.1"
|
||||||
resolved "https://registry.yarnpkg.com/mermaid/-/mermaid-10.3.1.tgz#2f3c7e9f6bd7a8da2bef71cce2a542c8eba2a62e"
|
resolved "https://registry.yarnpkg.com/mermaid/-/mermaid-10.6.1.tgz#701f4160484137a417770ce757ce1887a98c00fc"
|
||||||
integrity sha512-hkenh7WkuRWPcob3oJtrN3W+yzrrIYuWF1OIfk/d0xGE8UWlvDhfexaHmDwwe8DKQgqMLI8DWEPwGprxkumjuw==
|
integrity sha512-Hky0/RpOw/1il9X8AvzOEChfJtVvmXm+y7JML5C//ePYMy0/9jCEmW1E1g86x9oDfW9+iVEdTV/i+M6KWRNs4A==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@braintree/sanitize-url" "^6.0.1"
|
"@braintree/sanitize-url" "^6.0.1"
|
||||||
"@types/d3-scale" "^4.0.3"
|
"@types/d3-scale" "^4.0.3"
|
||||||
@@ -4690,10 +4685,10 @@ nanoid@^3.3.4:
|
|||||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c"
|
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c"
|
||||||
integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==
|
integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==
|
||||||
|
|
||||||
nanoid@^4.0.2:
|
nanoid@^5.0.3:
|
||||||
version "4.0.2"
|
version "5.0.3"
|
||||||
resolved "https://registry.npmmirror.com/nanoid/-/nanoid-4.0.2.tgz#140b3c5003959adbebf521c170f282c5e7f9fb9e"
|
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-5.0.3.tgz#6c97f53d793a7a1de6a38ebb46f50f95bf9793c7"
|
||||||
integrity sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==
|
integrity sha512-I7X2b22cxA4LIHXPSqbBCEQSL+1wv8TuoefejsX4HFWyC6jc5JG7CEaxOltiKjc1M+YCS2YkrZZcj4+dytw9GA==
|
||||||
|
|
||||||
natural-compare@^1.4.0:
|
natural-compare@^1.4.0:
|
||||||
version "1.4.0"
|
version "1.4.0"
|
||||||
@@ -5799,6 +5794,11 @@ unbox-primitive@^1.0.2:
|
|||||||
has-symbols "^1.0.3"
|
has-symbols "^1.0.3"
|
||||||
which-boxed-primitive "^1.0.2"
|
which-boxed-primitive "^1.0.2"
|
||||||
|
|
||||||
|
undici-types@~5.26.4:
|
||||||
|
version "5.26.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617"
|
||||||
|
integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==
|
||||||
|
|
||||||
unicode-canonical-property-names-ecmascript@^2.0.0:
|
unicode-canonical-property-names-ecmascript@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz#301acdc525631670d39f6146e0e77ff6bbdebddc"
|
resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz#301acdc525631670d39f6146e0e77ff6bbdebddc"
|
||||||
|