mirror of
https://github.com/Yidadaa/ChatGPT-Next-Web.git
synced 2025-08-31 19:49:03 +08:00
Compare commits
99 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 |
@@ -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
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:
|
||||||
|
41
README.md
41
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,14 +155,14 @@ 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 password, 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)
|
||||||
|
|
||||||
> Default: `https://api.openai.com`
|
> Default: `https://api.openai.com`
|
||||||
@@ -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
|
||||||
@@ -197,6 +213,15 @@ If you do want users to query balance, set this value to 1, or you should set it
|
|||||||
|
|
||||||
If you want to disable parse settings from url, set this to 1.
|
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
|
||||||
|
|
||||||
NodeJS >= 18, Docker >= 20
|
NodeJS >= 18, Docker >= 20
|
||||||
@@ -320,6 +345,10 @@ If you want to add a new translation, read this [document](./docs/translation.md
|
|||||||
[@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)
|
[@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
|
||||||
|
|
||||||
|
25
README_CN.md
25
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 即可。
|
||||||
@@ -106,6 +120,13 @@ OpenAI 接口代理 URL,如果你手动配置了 openai 接口代理,请填
|
|||||||
|
|
||||||
如果你想禁用从链接解析预制设置,将此环境变量设置为 1 即可。
|
如果你想禁用从链接解析预制设置,将此环境变量设置为 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`
|
||||||
|
|
||||||
|
用来控制模型列表,使用 `+` 增加一个模型,使用 `-` 来隐藏一个模型,使用 `模型名=展示名` 来自定义模型的展示名,用英文逗号隔开。
|
||||||
|
|
||||||
## 开发
|
## 开发
|
||||||
|
|
||||||
点击下方按钮,开始二次开发:
|
点击下方按钮,开始二次开发:
|
||||||
@@ -118,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
|
||||||
```
|
```
|
||||||
|
|
||||||
### 本地开发
|
### 本地开发
|
||||||
|
@@ -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,
|
||||||
|
@@ -12,6 +12,7 @@ const DANGER_CONFIG = {
|
|||||||
disableGPT4: serverConfig.disableGPT4,
|
disableGPT4: serverConfig.disableGPT4,
|
||||||
hideBalanceQuery: serverConfig.hideBalanceQuery,
|
hideBalanceQuery: serverConfig.hideBalanceQuery,
|
||||||
disableFastLink: serverConfig.disableFastLink,
|
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
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() {
|
||||||
|
@@ -18,7 +18,7 @@ export function AuthPage() {
|
|||||||
const goChat = () => navigate(Path.Chat);
|
const goChat = () => navigate(Path.Chat);
|
||||||
const resetAccessCode = () => {
|
const resetAccessCode = () => {
|
||||||
accessStore.update((access) => {
|
accessStore.update((access) => {
|
||||||
access.token = "";
|
access.openaiApiKey = "";
|
||||||
access.accessCode = "";
|
access.accessCode = "";
|
||||||
});
|
});
|
||||||
}; // Reset access code to empty string
|
}; // Reset access code to empty string
|
||||||
@@ -56,11 +56,11 @@ export function AuthPage() {
|
|||||||
<input
|
<input
|
||||||
className={styles["auth-input"]}
|
className={styles["auth-input"]}
|
||||||
type="password"
|
type="password"
|
||||||
placeholder={Locale.Settings.Token.Placeholder}
|
placeholder={Locale.Settings.Access.OpenAI.ApiKey.Placeholder}
|
||||||
value={accessStore.token}
|
value={accessStore.openaiApiKey}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
accessStore.update(
|
accessStore.update(
|
||||||
(access) => (access.token = e.currentTarget.value),
|
(access) => (access.openaiApiKey = e.currentTarget.value),
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@@ -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);
|
||||||
|
@@ -88,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 />,
|
||||||
@@ -143,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>
|
||||||
@@ -429,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 && (
|
||||||
@@ -518,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) => {
|
||||||
@@ -1001,7 +1013,9 @@ function _Chat() {
|
|||||||
).then((res) => {
|
).then((res) => {
|
||||||
if (!res) return;
|
if (!res) return;
|
||||||
if (payload.key) {
|
if (payload.key) {
|
||||||
accessStore.update((access) => (access.token = payload.key!));
|
accessStore.update(
|
||||||
|
(access) => (access.openaiApiKey = payload.key!),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (payload.url) {
|
if (payload.url) {
|
||||||
accessStore.update((access) => (access.openaiUrl = payload.url!));
|
accessStore.update((access) => (access.openaiUrl = payload.url!));
|
||||||
@@ -1161,7 +1175,12 @@ function _Chat() {
|
|||||||
{["system"].includes(message.role) ? (
|
{["system"].includes(message.role) ? (
|
||||||
<Avatar avatar="2699-fe0f" />
|
<Avatar avatar="2699-fe0f" />
|
||||||
) : (
|
) : (
|
||||||
<MaskAvatar mask={session.mask} />
|
<MaskAvatar
|
||||||
|
avatar={session.mask.avatar}
|
||||||
|
model={
|
||||||
|
message.model || session.mask.modelConfig.model
|
||||||
|
}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
@@ -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}>
|
||||||
@@ -437,13 +453,13 @@ export function ImagePreviewer(props: {
|
|||||||
showToast(Locale.Export.Image.Toast);
|
showToast(Locale.Export.Image.Toast);
|
||||||
const dom = previewRef.current;
|
const dom = previewRef.current;
|
||||||
if (!dom) return;
|
if (!dom) return;
|
||||||
|
|
||||||
const isApp = getClientConfig()?.isApp;
|
const isApp = getClientConfig()?.isApp;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const blob = await toPng(dom);
|
const blob = await toPng(dom);
|
||||||
if (!blob) return;
|
if (!blob) return;
|
||||||
|
|
||||||
if (isMobile || (isApp && window.__TAURI__)) {
|
if (isMobile || (isApp && window.__TAURI__)) {
|
||||||
if (isApp && window.__TAURI__) {
|
if (isApp && window.__TAURI__) {
|
||||||
const result = await window.__TAURI__.dialog.save({
|
const result = await window.__TAURI__.dialog.save({
|
||||||
@@ -459,7 +475,7 @@ export function ImagePreviewer(props: {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
if (result !== null) {
|
if (result !== null) {
|
||||||
const response = await fetch(blob);
|
const response = await fetch(blob);
|
||||||
const buffer = await response.arrayBuffer();
|
const buffer = await response.arrayBuffer();
|
||||||
@@ -604,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;
|
||||||
|
@@ -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>
|
||||||
@@ -398,7 +402,7 @@ export function MaskPage() {
|
|||||||
setSearchText(text);
|
setSearchText(text);
|
||||||
if (text.length > 0) {
|
if (text.length > 0) {
|
||||||
const result = allMasks.filter((m) =>
|
const result = allMasks.filter((m) =>
|
||||||
m.name.toLowerCase().includes(text.toLowerCase())
|
m.name.toLowerCase().includes(text.toLowerCase()),
|
||||||
);
|
);
|
||||||
setSearchMasks(result);
|
setSearchMasks(result);
|
||||||
} else {
|
} else {
|
||||||
@@ -523,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>
|
||||||
);
|
);
|
||||||
|
@@ -51,10 +51,13 @@ import Locale, {
|
|||||||
import { copyToClipboard } from "../utils";
|
import { copyToClipboard } from "../utils";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import {
|
import {
|
||||||
|
Azure,
|
||||||
OPENAI_BASE_URL,
|
OPENAI_BASE_URL,
|
||||||
Path,
|
Path,
|
||||||
RELEASE_URL,
|
RELEASE_URL,
|
||||||
STORAGE_KEY,
|
STORAGE_KEY,
|
||||||
|
ServiceProvider,
|
||||||
|
SlotID,
|
||||||
UPDATE_URL,
|
UPDATE_URL,
|
||||||
} from "../constant";
|
} from "../constant";
|
||||||
import { Prompt, SearchService, usePromptStore } from "../store/prompt";
|
import { Prompt, SearchService, usePromptStore } from "../store/prompt";
|
||||||
@@ -580,8 +583,16 @@ export function Settings() {
|
|||||||
const accessStore = useAccessStore();
|
const accessStore = useAccessStore();
|
||||||
const shouldHideBalanceQuery = useMemo(() => {
|
const shouldHideBalanceQuery = useMemo(() => {
|
||||||
const isOpenAiUrl = accessStore.openaiUrl.includes(OPENAI_BASE_URL);
|
const isOpenAiUrl = accessStore.openaiUrl.includes(OPENAI_BASE_URL);
|
||||||
return accessStore.hideBalanceQuery || isOpenAiUrl;
|
return (
|
||||||
}, [accessStore.hideBalanceQuery, accessStore.openaiUrl]);
|
accessStore.hideBalanceQuery ||
|
||||||
|
isOpenAiUrl ||
|
||||||
|
accessStore.provider === ServiceProvider.Azure
|
||||||
|
);
|
||||||
|
}, [
|
||||||
|
accessStore.hideBalanceQuery,
|
||||||
|
accessStore.openaiUrl,
|
||||||
|
accessStore.provider,
|
||||||
|
]);
|
||||||
|
|
||||||
const usage = {
|
const usage = {
|
||||||
used: updateStore.used,
|
used: updateStore.used,
|
||||||
@@ -624,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);
|
||||||
@@ -877,16 +893,16 @@ 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.update(
|
accessStore.update(
|
||||||
(access) => (access.accessCode = e.currentTarget.value),
|
(access) => (access.accessCode = e.currentTarget.value),
|
||||||
@@ -894,46 +910,159 @@ export function Settings() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</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.update(
|
checked={accessStore.useCustomConfig}
|
||||||
(access) => (access.openaiUrl = e.currentTarget.value),
|
onChange={(e) =>
|
||||||
)
|
accessStore.update(
|
||||||
}
|
(access) =>
|
||||||
></input>
|
(access.useCustomConfig = e.currentTarget.checked),
|
||||||
</ListItem>
|
)
|
||||||
<ListItem
|
}
|
||||||
title={Locale.Settings.Token.Title}
|
></input>
|
||||||
subTitle={Locale.Settings.Token.SubTitle}
|
</ListItem>
|
||||||
>
|
)
|
||||||
<PasswordInput
|
}
|
||||||
value={accessStore.token}
|
{accessStore.useCustomConfig && (
|
||||||
type="text"
|
<>
|
||||||
placeholder={Locale.Settings.Token.Placeholder}
|
<ListItem
|
||||||
onChange={(e) => {
|
title={Locale.Settings.Access.Provider.Title}
|
||||||
accessStore.update(
|
subTitle={Locale.Settings.Access.Provider.SubTitle}
|
||||||
(access) => (access.token = e.currentTarget.value),
|
>
|
||||||
);
|
<Select
|
||||||
}}
|
value={accessStore.provider}
|
||||||
/>
|
onChange={(e) => {
|
||||||
</ListItem>
|
accessStore.update(
|
||||||
</>
|
(access) =>
|
||||||
) : null}
|
(access.provider = e.target
|
||||||
|
.value as ServiceProvider),
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Object.entries(ServiceProvider).map(([k, v]) => (
|
||||||
|
<option value={v} key={k}>
|
||||||
|
{k}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</ListItem>
|
||||||
|
|
||||||
{!shouldHideBalanceQuery ? (
|
{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={
|
||||||
@@ -960,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, useMemo } 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";
|
||||||
@@ -202,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,19 +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_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
|
ENABLE_BALANCE_QUERY?: string; // allow user to query balance or not
|
||||||
DISABLE_FAST_LINK?: string; // disallow parse settings from url 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -38,17 +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.ENABLE_BALANCE_QUERY,
|
hideBalanceQuery: !process.env.ENABLE_BALANCE_QUERY,
|
||||||
disableFastLink: !!process.env.DISABLE_FAST_LINK,
|
disableFastLink: !!process.env.DISABLE_FAST_LINK,
|
||||||
|
customModels,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@@ -8,7 +8,7 @@ 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://ab.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 const OPENAI_BASE_URL = "https://api.openai.com";
|
||||||
|
|
||||||
@@ -23,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 {
|
||||||
@@ -60,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",
|
||||||
@@ -67,19 +74,24 @@ 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: {{cutoff}}
|
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> = {
|
export const KnowledgeCutOffDate: Record<string, string> = {
|
||||||
default: "2021-09",
|
default: "2021-09",
|
||||||
"gpt-3.5-turbo-1106": "2023-04",
|
|
||||||
"gpt-4-1106-preview": "2023-04",
|
"gpt-4-1106-preview": "2023-04",
|
||||||
"gpt-4-vision-preview": "2023-04",
|
"gpt-4-vision-preview": "2023-04",
|
||||||
};
|
};
|
||||||
|
@@ -167,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) {
|
||||||
@@ -181,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: "الحرارة",
|
||||||
|
@@ -199,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) {
|
||||||
@@ -213,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: "তাপমাত্রা",
|
||||||
|
@@ -85,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 图片",
|
||||||
@@ -258,11 +258,6 @@ const cn = {
|
|||||||
Title: "历史消息长度压缩阈值",
|
Title: "历史消息长度压缩阈值",
|
||||||
SubTitle: "当未压缩的历史消息超过该值时,将进行压缩",
|
SubTitle: "当未压缩的历史消息超过该值时,将进行压缩",
|
||||||
},
|
},
|
||||||
Token: {
|
|
||||||
Title: "API Key",
|
|
||||||
SubTitle: "使用自己的 Key 可绕过密码访问限制",
|
|
||||||
Placeholder: "OpenAI API Key",
|
|
||||||
},
|
|
||||||
|
|
||||||
Usage: {
|
Usage: {
|
||||||
Title: "余额查询",
|
Title: "余额查询",
|
||||||
@@ -273,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)",
|
||||||
@@ -409,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
|
||||||
|
@@ -262,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) {
|
||||||
@@ -276,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",
|
||||||
@@ -410,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",
|
||||||
|
@@ -4,8 +4,9 @@ import { PartialLocaleType } from "./index";
|
|||||||
const id: PartialLocaleType = {
|
const id: PartialLocaleType = {
|
||||||
WIP: "Coming Soon...",
|
WIP: "Coming Soon...",
|
||||||
Error: {
|
Error: {
|
||||||
Unauthorized: "Akses tidak diizinkan, silakan masukkan kode akses atau masukkan kunci API OpenAI Anda. di halaman [autentikasi](/#/auth) atau di halaman [Pengaturan](/#/settings).",
|
Unauthorized:
|
||||||
},
|
"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",
|
||||||
@@ -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",
|
||||||
@@ -379,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
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",
|
||||||
|
@@ -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) {
|
||||||
@@ -134,11 +130,7 @@ const tw: PartialLocaleType = {
|
|||||||
Check: "重新檢查",
|
Check: "重新檢查",
|
||||||
NoAccess: "輸入 API Key 檢視餘額",
|
NoAccess: "輸入 API Key 檢視餘額",
|
||||||
},
|
},
|
||||||
AccessCode: {
|
|
||||||
Title: "授權碼",
|
|
||||||
SubTitle: "目前是未授權存取狀態",
|
|
||||||
Placeholder: "請輸入授權碼",
|
|
||||||
},
|
|
||||||
Model: "模型 (model)",
|
Model: "模型 (model)",
|
||||||
Temperature: {
|
Temperature: {
|
||||||
Title: "隨機性 (temperature)",
|
Title: "隨機性 (temperature)",
|
||||||
|
@@ -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)",
|
||||||
|
@@ -1,24 +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,
|
disableFastLink: false,
|
||||||
|
customModels: "",
|
||||||
openaiUrl: DEFAULT_OPENAI_URL,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useAccessStore = createPersistStore(
|
export const useAccessStore = createPersistStore(
|
||||||
@@ -30,12 +47,24 @@ export const useAccessStore = createPersistStore(
|
|||||||
|
|
||||||
return get().needCode;
|
return get().needCode;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
isValidOpenAI() {
|
||||||
|
return ensure(get(), ["openaiApiKey"]);
|
||||||
|
},
|
||||||
|
|
||||||
|
isValidAzure() {
|
||||||
|
return ensure(get(), ["azureUrl", "azureApiKey", "azureApiVersion"]);
|
||||||
|
},
|
||||||
|
|
||||||
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() {
|
||||||
@@ -52,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");
|
||||||
@@ -69,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;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@@ -85,33 +85,6 @@ 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);
|
||||||
}
|
}
|
||||||
@@ -584,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);
|
||||||
|
@@ -49,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,
|
||||||
@@ -82,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);
|
||||||
@@ -128,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 allModels = get().models.concat(customModels);
|
|
||||||
allModels.sort((a, b) => (a.name < b.name ? -1 : 1));
|
|
||||||
return allModels;
|
|
||||||
},
|
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
name: StoreKey.Config,
|
name: StoreKey.Config,
|
||||||
|
@@ -357,3 +357,7 @@ pre {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.copyable {
|
||||||
|
user-select: text;
|
||||||
|
}
|
||||||
|
@@ -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) {
|
||||||
|
@@ -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] !== "",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
16
app/utils/hooks.ts
Normal file
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
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".
|
||||||
|
10
package.json
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",
|
||||||
|
@@ -9,7 +9,7 @@
|
|||||||
},
|
},
|
||||||
"package": {
|
"package": {
|
||||||
"productName": "ChatGPT Next Web",
|
"productName": "ChatGPT Next Web",
|
||||||
"version": "2.9.10"
|
"version": "2.9.13"
|
||||||
},
|
},
|
||||||
"tauri": {
|
"tauri": {
|
||||||
"allowlist": {
|
"allowlist": {
|
||||||
|
21
vercel.json
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
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"
|
||||||
|
Reference in New Issue
Block a user