mirror of
https://github.com/Yidadaa/ChatGPT-Next-Web.git
synced 2025-08-31 03:09:04 +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
|
||||
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)
|
||||
# Default: Empty
|
||||
# If you do not want users to use GPT-4, set this value to 1.
|
||||
@@ -29,5 +24,15 @@ DISABLE_GPT4=
|
||||
|
||||
# (optional)
|
||||
# Default: Empty
|
||||
# If you do not want users to query balance, set this value to 1.
|
||||
HIDE_BALANCE_QUERY=
|
||||
# If you do not want users to input their own API key, set this value to 1.
|
||||
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
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
node-version: 18
|
||||
- name: get version
|
||||
run: echo "PACKAGE_VERSION=$(node -p "require('./src-tauri/tauri.conf.json').package.version")" >> $GITHUB_ENV
|
||||
- name: create release
|
||||
@@ -59,7 +59,7 @@ jobs:
|
||||
- name: setup node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
node-version: 18
|
||||
- name: install Rust stable
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
|
41
README.md
41
README.md
@@ -3,7 +3,7 @@
|
||||
|
||||
<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.
|
||||
|
||||
@@ -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.7 let's share conversations as image, or share to ShareGPT!
|
||||
- 🚀 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)
|
||||
- 自动压缩上下文聊天记录,在节省 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
|
||||
- 🚀 v2.7 现在可以将会话分享为图片了,也可以分享到 ShareGPT 的在线链接。
|
||||
- 🚀 v2.8 发布了横跨 Linux/Windows/MacOS 的体积极小的客户端。
|
||||
- 🚀 v2.9.11 现在可以使用自定义 Azure 服务了。
|
||||
|
||||
## Get Started
|
||||
|
||||
@@ -153,14 +155,14 @@ After adding or modifying this environment variable, please redeploy the project
|
||||
|
||||
> [简体中文 > 如何配置 api key、访问密码、接口代理](./README_CN.md#环境变量)
|
||||
|
||||
### `OPENAI_API_KEY` (required)
|
||||
|
||||
Your openai api key.
|
||||
|
||||
### `CODE` (optional)
|
||||
|
||||
Access password, separated by comma.
|
||||
|
||||
### `OPENAI_API_KEY` (required)
|
||||
|
||||
Your openai api key, join multiple api keys with comma.
|
||||
|
||||
### `BASE_URL` (optional)
|
||||
|
||||
> Default: `https://api.openai.com`
|
||||
@@ -173,6 +175,20 @@ Override openai api request base url.
|
||||
|
||||
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)
|
||||
|
||||
> 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.
|
||||
|
||||
### `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
|
||||
|
||||
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)
|
||||
[@piksonGit](https://github.com/piksonGit)
|
||||
[@ouyangzhiping](https://github.com/ouyangzhiping)
|
||||
[@wenjiavv](https://github.com/wenjiavv)
|
||||
[@LeXwDeX](https://github.com/LeXwDeX)
|
||||
[@Licoy](https://github.com/Licoy)
|
||||
[@shangmin2009](https://github.com/shangmin2009)
|
||||
|
||||
### Contributor
|
||||
|
||||
|
25
README_CN.md
25
README_CN.md
@@ -68,7 +68,7 @@ code1,code2,code3
|
||||
|
||||
### `OPENAI_API_KEY` (必填项)
|
||||
|
||||
OpanAI 密钥,你在 openai 账户页面申请的 api key。
|
||||
OpanAI 密钥,你在 openai 账户页面申请的 api key,使用英文逗号隔开多个 key,这样可以随机轮询这些 key。
|
||||
|
||||
### `CODE` (可选)
|
||||
|
||||
@@ -90,6 +90,20 @@ OpenAI 接口代理 URL,如果你手动配置了 openai 接口代理,请填
|
||||
|
||||
指定 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` (可选)
|
||||
|
||||
如果你不想让用户自行填入 API Key,将此环境变量设置为 1 即可。
|
||||
@@ -106,6 +120,13 @@ OpenAI 接口代理 URL,如果你手动配置了 openai 接口代理,请填
|
||||
|
||||
如果你想禁用从链接解析预制设置,将此环境变量设置为 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>
|
||||
|
||||
# 中国大陆用户,可以使用本项目自带的代理进行开发,你也可以自由选择其他代理地址
|
||||
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") ?? "";
|
||||
|
||||
// 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();
|
||||
|
||||
@@ -39,19 +39,32 @@ export function auth(req: NextRequest) {
|
||||
console.log("[User IP] ", getIP(req));
|
||||
console.log("[Time] ", new Date().toLocaleString());
|
||||
|
||||
if (serverConfig.needCode && !serverConfig.codes.has(hashedCode) && !token) {
|
||||
if (serverConfig.needCode && !serverConfig.codes.has(hashedCode) && !apiKey) {
|
||||
return {
|
||||
error: true,
|
||||
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 (!token) {
|
||||
const apiKey = serverConfig.apiKey;
|
||||
if (apiKey) {
|
||||
if (!apiKey) {
|
||||
const serverApiKey = serverConfig.isAzure
|
||||
? serverConfig.azureApiKey
|
||||
: serverConfig.apiKey;
|
||||
|
||||
if (serverApiKey) {
|
||||
console.log("[Auth] use system api key");
|
||||
req.headers.set("Authorization", `Bearer ${apiKey}`);
|
||||
req.headers.set(
|
||||
"Authorization",
|
||||
`${serverConfig.isAzure ? "" : "Bearer "}${serverApiKey}`,
|
||||
);
|
||||
} else {
|
||||
console.log("[Auth] admin did not provide an api key");
|
||||
}
|
||||
|
@@ -1,48 +1,65 @@
|
||||
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 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;
|
||||
const serverConfig = getServerSideConfig();
|
||||
|
||||
export async function requestOpenai(req: NextRequest) {
|
||||
const controller = new AbortController();
|
||||
|
||||
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/",
|
||||
"",
|
||||
);
|
||||
|
||||
let baseUrl = BASE_URL;
|
||||
let baseUrl =
|
||||
serverConfig.azureUrl || serverConfig.baseUrl || OPENAI_BASE_URL;
|
||||
|
||||
if (!baseUrl.startsWith("http")) {
|
||||
baseUrl = `${PROTOCOL}://${baseUrl}`;
|
||||
baseUrl = `https://${baseUrl}`;
|
||||
}
|
||||
|
||||
if (baseUrl.endsWith('/')) {
|
||||
if (baseUrl.endsWith("/")) {
|
||||
baseUrl = baseUrl.slice(0, -1);
|
||||
}
|
||||
|
||||
console.log("[Proxy] ", openaiPath);
|
||||
console.log("[Proxy] ", path);
|
||||
console.log("[Base Url]", baseUrl);
|
||||
|
||||
if (process.env.OPENAI_ORG_ID) {
|
||||
console.log("[Org ID]", process.env.OPENAI_ORG_ID);
|
||||
// this fix [Org ID] undefined in server side if not using custom point
|
||||
if (serverConfig.openaiOrgId !== undefined) {
|
||||
console.log("[Org ID]", serverConfig.openaiOrgId);
|
||||
}
|
||||
|
||||
const timeoutId = setTimeout(() => {
|
||||
controller.abort();
|
||||
}, 10 * 60 * 1000);
|
||||
const timeoutId = setTimeout(
|
||||
() => {
|
||||
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 = {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Cache-Control": "no-store",
|
||||
Authorization: authValue,
|
||||
...(process.env.OPENAI_ORG_ID && {
|
||||
"OpenAI-Organization": process.env.OPENAI_ORG_ID,
|
||||
[authHeaderName]: authValue,
|
||||
...(serverConfig.openaiOrgId && {
|
||||
"OpenAI-Organization": serverConfig.openaiOrgId,
|
||||
}),
|
||||
},
|
||||
method: req.method,
|
||||
@@ -55,18 +72,23 @@ export async function requestOpenai(req: NextRequest) {
|
||||
};
|
||||
|
||||
// #1815 try to refuse gpt4 request
|
||||
if (DISABLE_GPT4 && req.body) {
|
||||
if (serverConfig.customModels && req.body) {
|
||||
try {
|
||||
const modelTable = collectModelTable(
|
||||
DEFAULT_MODELS,
|
||||
serverConfig.customModels,
|
||||
);
|
||||
const clonedBody = await req.text();
|
||||
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(
|
||||
{
|
||||
error: true,
|
||||
message: "you are not allowed to use gpt-4 model",
|
||||
message: `you are not allowed to use ${jsonBody?.model} model`,
|
||||
},
|
||||
{
|
||||
status: 403,
|
||||
|
@@ -12,6 +12,7 @@ const DANGER_CONFIG = {
|
||||
disableGPT4: serverConfig.disableGPT4,
|
||||
hideBalanceQuery: serverConfig.hideBalanceQuery,
|
||||
disableFastLink: serverConfig.disableFastLink,
|
||||
customModels: serverConfig.customModels,
|
||||
};
|
||||
|
||||
declare global {
|
||||
|
@@ -75,3 +75,4 @@ export const GET = handle;
|
||||
export const POST = handle;
|
||||
|
||||
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 { ACCESS_CODE_PREFIX } from "../constant";
|
||||
import { ACCESS_CODE_PREFIX, Azure, ServiceProvider } from "../constant";
|
||||
import { ChatMessage, ModelType, useAccessStore } from "../store";
|
||||
import { ChatGPTApi } from "./platforms/openai";
|
||||
|
||||
@@ -127,22 +127,26 @@ export const api = new ClientApi();
|
||||
|
||||
export function getHeaders() {
|
||||
const accessStore = useAccessStore.getState();
|
||||
let headers: Record<string, string> = {
|
||||
const headers: Record<string, string> = {
|
||||
"Content-Type": "application/json",
|
||||
"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;
|
||||
|
||||
// use user's api key first
|
||||
if (validString(accessStore.token)) {
|
||||
headers.Authorization = makeBearer(accessStore.token);
|
||||
if (validString(apiKey)) {
|
||||
headers[authHeader] = makeBearer(apiKey);
|
||||
} else if (
|
||||
accessStore.enabledAccessControl() &&
|
||||
validString(accessStore.accessCode)
|
||||
) {
|
||||
headers.Authorization = makeBearer(
|
||||
headers[authHeader] = makeBearer(
|
||||
ACCESS_CODE_PREFIX + accessStore.accessCode,
|
||||
);
|
||||
}
|
||||
|
@@ -1,8 +1,10 @@
|
||||
import {
|
||||
ApiPath,
|
||||
DEFAULT_API_HOST,
|
||||
DEFAULT_MODELS,
|
||||
OpenaiPath,
|
||||
REQUEST_TIMEOUT_MS,
|
||||
ServiceProvider,
|
||||
} from "@/app/constant";
|
||||
import { useAccessStore, useAppConfig, useChatStore } from "@/app/store";
|
||||
|
||||
@@ -14,6 +16,7 @@ import {
|
||||
} from "@fortaine/fetch-event-source";
|
||||
import { prettyObject } from "@/app/utils/format";
|
||||
import { getClientConfig } from "@/app/config/client";
|
||||
import { makeAzurePath } from "@/app/azure";
|
||||
|
||||
export interface OpenAIListModelResponse {
|
||||
object: string;
|
||||
@@ -28,20 +31,35 @@ export class ChatGPTApi implements LLMApi {
|
||||
private disableListModels = true;
|
||||
|
||||
path(path: string): string {
|
||||
let openaiUrl = useAccessStore.getState().openaiUrl;
|
||||
const apiPath = "/api/openai";
|
||||
const accessStore = useAccessStore.getState();
|
||||
|
||||
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;
|
||||
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)) {
|
||||
openaiUrl = "https://" + openaiUrl;
|
||||
if (!baseUrl.startsWith("http") && !baseUrl.startsWith(ApiPath.OpenAI)) {
|
||||
baseUrl = "https://" + baseUrl;
|
||||
}
|
||||
return [openaiUrl, path].join("/");
|
||||
|
||||
if (isAzure) {
|
||||
path = makeAzurePath(path, accessStore.azureApiVersion);
|
||||
}
|
||||
|
||||
return [baseUrl, path].join("/");
|
||||
}
|
||||
|
||||
extractMessage(res: any) {
|
||||
@@ -70,6 +88,8 @@ export class ChatGPTApi implements LLMApi {
|
||||
presence_penalty: modelConfig.presence_penalty,
|
||||
frequency_penalty: modelConfig.frequency_penalty,
|
||||
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);
|
||||
@@ -95,12 +115,35 @@ export class ChatGPTApi implements LLMApi {
|
||||
|
||||
if (shouldStream) {
|
||||
let responseText = "";
|
||||
let remainText = "";
|
||||
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 = () => {
|
||||
if (!finished) {
|
||||
options.onFinish(responseText);
|
||||
finished = true;
|
||||
options.onFinish(responseText + remainText);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -154,14 +197,19 @@ export class ChatGPTApi implements LLMApi {
|
||||
}
|
||||
const text = msg.data;
|
||||
try {
|
||||
const json = JSON.parse(text);
|
||||
const delta = json.choices[0].delta.content;
|
||||
const json = JSON.parse(text) as {
|
||||
choices: Array<{
|
||||
delta: {
|
||||
content: string;
|
||||
};
|
||||
}>;
|
||||
};
|
||||
const delta = json.choices[0]?.delta?.content;
|
||||
if (delta) {
|
||||
responseText += delta;
|
||||
options.onUpdate?.(responseText, delta);
|
||||
remainText += delta;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("[Request] parse error", text, msg);
|
||||
console.error("[Request] parse error", text);
|
||||
}
|
||||
},
|
||||
onclose() {
|
||||
|
@@ -18,7 +18,7 @@ export function AuthPage() {
|
||||
const goChat = () => navigate(Path.Chat);
|
||||
const resetAccessCode = () => {
|
||||
accessStore.update((access) => {
|
||||
access.token = "";
|
||||
access.openaiApiKey = "";
|
||||
access.accessCode = "";
|
||||
});
|
||||
}; // Reset access code to empty string
|
||||
@@ -56,11 +56,11 @@ export function AuthPage() {
|
||||
<input
|
||||
className={styles["auth-input"]}
|
||||
type="password"
|
||||
placeholder={Locale.Settings.Token.Placeholder}
|
||||
value={accessStore.token}
|
||||
placeholder={Locale.Settings.Access.OpenAI.ApiKey.Placeholder}
|
||||
value={accessStore.openaiApiKey}
|
||||
onChange={(e) => {
|
||||
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 { useRef, useEffect } from "react";
|
||||
import { showConfirm } from "./ui-lib";
|
||||
import { useMobileScreen } from "../utils";
|
||||
|
||||
export function ChatItem(props: {
|
||||
onClick?: () => void;
|
||||
@@ -60,7 +61,10 @@ export function ChatItem(props: {
|
||||
{props.narrow ? (
|
||||
<div className={styles["chat-item-narrow"]}>
|
||||
<div className={styles["chat-item-avatar"] + " no-dark"}>
|
||||
<MaskAvatar mask={props.mask} />
|
||||
<MaskAvatar
|
||||
avatar={props.mask.avatar}
|
||||
model={props.mask.modelConfig.model}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles["chat-item-narrow-count"]}>
|
||||
{props.count}
|
||||
@@ -80,7 +84,11 @@ export function ChatItem(props: {
|
||||
|
||||
<div
|
||||
className={styles["chat-item-delete"]}
|
||||
onClickCapture={props.onDelete}
|
||||
onClickCapture={(e) => {
|
||||
props.onDelete?.();
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</div>
|
||||
@@ -101,6 +109,7 @@ export function ChatList(props: { narrow?: boolean }) {
|
||||
);
|
||||
const chatStore = useChatStore();
|
||||
const navigate = useNavigate();
|
||||
const isMobileScreen = useMobileScreen();
|
||||
|
||||
const onDragEnd: OnDragEndResponder = (result) => {
|
||||
const { destination, source } = result;
|
||||
@@ -142,7 +151,7 @@ export function ChatList(props: { narrow?: boolean }) {
|
||||
}}
|
||||
onDelete={async () => {
|
||||
if (
|
||||
!props.narrow ||
|
||||
(!props.narrow && !isMobileScreen) ||
|
||||
(await showConfirm(Locale.Home.DeleteChat))
|
||||
) {
|
||||
chatStore.deleteSession(i);
|
||||
|
@@ -88,6 +88,7 @@ import { ChatCommandPrefix, useChatCommand, useCommand } from "../command";
|
||||
import { prettyObject } from "../utils/format";
|
||||
import { ExportMessageModal } from "./exporter";
|
||||
import { getClientConfig } from "../config/client";
|
||||
import { useAllModels } from "../utils/hooks";
|
||||
|
||||
const Markdown = dynamic(async () => (await import("./markdown")).Markdown, {
|
||||
loading: () => <LoadingIcon />,
|
||||
@@ -143,6 +144,7 @@ export function SessionConfigModel(props: { onClose: () => void }) {
|
||||
extraListItems={
|
||||
session.mask.modelConfig.sendMemory ? (
|
||||
<ListItem
|
||||
className="copyable"
|
||||
title={`${Locale.Memory.Title} (${session.lastSummarizeIndex} of ${session.messages.length})`}
|
||||
subTitle={session.memoryPrompt || Locale.Memory.EmptyContent}
|
||||
></ListItem>
|
||||
@@ -429,16 +431,26 @@ export function ChatActions(props: {
|
||||
|
||||
// switch model
|
||||
const currentModel = chatStore.currentSession().mask.modelConfig.model;
|
||||
const allModels = useAllModels();
|
||||
const models = useMemo(
|
||||
() =>
|
||||
config
|
||||
.allModels()
|
||||
.filter((m) => m.available)
|
||||
.map((m) => m.name),
|
||||
[config],
|
||||
() => allModels.filter((m) => m.available),
|
||||
[allModels],
|
||||
);
|
||||
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 (
|
||||
<div className={styles["chat-input-actions"]}>
|
||||
{couldStop && (
|
||||
@@ -518,8 +530,8 @@ export function ChatActions(props: {
|
||||
<Selector
|
||||
defaultSelectedValue={currentModel}
|
||||
items={models.map((m) => ({
|
||||
title: m,
|
||||
value: m,
|
||||
title: m.displayName,
|
||||
value: m.name,
|
||||
}))}
|
||||
onClose={() => setShowModelSelector(false)}
|
||||
onSelection={(s) => {
|
||||
@@ -1001,7 +1013,9 @@ function _Chat() {
|
||||
).then((res) => {
|
||||
if (!res) return;
|
||||
if (payload.key) {
|
||||
accessStore.update((access) => (access.token = payload.key!));
|
||||
accessStore.update(
|
||||
(access) => (access.openaiApiKey = payload.key!),
|
||||
);
|
||||
}
|
||||
if (payload.url) {
|
||||
accessStore.update((access) => (access.openaiUrl = payload.url!));
|
||||
@@ -1161,7 +1175,12 @@ function _Chat() {
|
||||
{["system"].includes(message.role) ? (
|
||||
<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);
|
||||
border: var(--border-in-light);
|
||||
|
||||
*:not(li) {
|
||||
code,
|
||||
pre {
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/* 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 styles from "./exporter.module.scss";
|
||||
import {
|
||||
@@ -27,7 +27,7 @@ import { Avatar } from "./emoji";
|
||||
import dynamic from "next/dynamic";
|
||||
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 { api } from "../client/api";
|
||||
import { prettyObject } from "../utils/format";
|
||||
@@ -41,7 +41,22 @@ const Markdown = dynamic(async () => (await import("./markdown")).Markdown, {
|
||||
export function ExportMessageModal(props: { onClose: () => void }) {
|
||||
return (
|
||||
<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" }}>
|
||||
<MessageExporter />
|
||||
</div>
|
||||
@@ -149,7 +164,7 @@ export function MessageExporter() {
|
||||
if (exportConfig.includeContext) {
|
||||
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;
|
||||
}, [
|
||||
exportConfig.includeContext,
|
||||
@@ -260,7 +275,8 @@ export function RenderExport(props: {
|
||||
});
|
||||
|
||||
props.onRender(renderMsgs);
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div ref={domRef}>
|
||||
@@ -437,13 +453,13 @@ export function ImagePreviewer(props: {
|
||||
showToast(Locale.Export.Image.Toast);
|
||||
const dom = previewRef.current;
|
||||
if (!dom) return;
|
||||
|
||||
|
||||
const isApp = getClientConfig()?.isApp;
|
||||
|
||||
|
||||
try {
|
||||
const blob = await toPng(dom);
|
||||
if (!blob) return;
|
||||
|
||||
|
||||
if (isMobile || (isApp && window.__TAURI__)) {
|
||||
if (isApp && window.__TAURI__) {
|
||||
const result = await window.__TAURI__.dialog.save({
|
||||
@@ -459,7 +475,7 @@ export function ImagePreviewer(props: {
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
|
||||
if (result !== null) {
|
||||
const response = await fetch(blob);
|
||||
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: {
|
||||
messages: ChatMessage[];
|
||||
topic: string;
|
||||
|
@@ -5,13 +5,13 @@ import RemarkBreaks from "remark-breaks";
|
||||
import RehypeKatex from "rehype-katex";
|
||||
import RemarkGfm from "remark-gfm";
|
||||
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 mermaid from "mermaid";
|
||||
|
||||
import LoadingIcon from "../icons/three-dots.svg";
|
||||
import React from "react";
|
||||
import { useDebouncedCallback, useThrottledCallback } from "use-debounce";
|
||||
import { useDebouncedCallback } from "use-debounce";
|
||||
import { showImageModal } from "./ui-lib";
|
||||
|
||||
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 }) {
|
||||
const escapedContent = useMemo(
|
||||
() => escapeDollarNumber(props.content),
|
||||
[props.content],
|
||||
);
|
||||
|
||||
return (
|
||||
<ReactMarkdown
|
||||
remarkPlugins={[RemarkMath, RemarkGfm, RemarkBreaks]}
|
||||
@@ -124,7 +146,7 @@ function _MarkDownContent(props: { content: string }) {
|
||||
},
|
||||
}}
|
||||
>
|
||||
{props.content}
|
||||
{escapedContent}
|
||||
</ReactMarkdown>
|
||||
);
|
||||
}
|
||||
|
@@ -18,6 +18,7 @@ import {
|
||||
ChatMessage,
|
||||
createMessage,
|
||||
ModelConfig,
|
||||
ModelType,
|
||||
useAppConfig,
|
||||
useChatStore,
|
||||
} from "../store";
|
||||
@@ -58,11 +59,11 @@ function reorder<T>(list: T[], startIndex: number, endIndex: number): T[] {
|
||||
return result;
|
||||
}
|
||||
|
||||
export function MaskAvatar(props: { mask: Mask }) {
|
||||
return props.mask.avatar !== DEFAULT_MASK_AVATAR ? (
|
||||
<Avatar avatar={props.mask.avatar} />
|
||||
export function MaskAvatar(props: { avatar: string; model?: ModelType }) {
|
||||
return props.avatar !== DEFAULT_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)}
|
||||
style={{ cursor: "pointer" }}
|
||||
>
|
||||
<MaskAvatar mask={props.mask} />
|
||||
<MaskAvatar
|
||||
avatar={props.mask.avatar}
|
||||
model={props.mask.modelConfig.model}
|
||||
/>
|
||||
</div>
|
||||
</Popover>
|
||||
</ListItem>
|
||||
@@ -398,7 +402,7 @@ export function MaskPage() {
|
||||
setSearchText(text);
|
||||
if (text.length > 0) {
|
||||
const result = allMasks.filter((m) =>
|
||||
m.name.toLowerCase().includes(text.toLowerCase())
|
||||
m.name.toLowerCase().includes(text.toLowerCase()),
|
||||
);
|
||||
setSearchMasks(result);
|
||||
} else {
|
||||
@@ -523,7 +527,7 @@ export function MaskPage() {
|
||||
<div className={styles["mask-item"]} key={m.id}>
|
||||
<div className={styles["mask-header"]}>
|
||||
<div className={styles["mask-icon"]}>
|
||||
<MaskAvatar mask={m} />
|
||||
<MaskAvatar avatar={m.avatar} model={m.modelConfig.model} />
|
||||
</div>
|
||||
<div className={styles["mask-title"]}>
|
||||
<div className={styles["mask-name"]}>{m.name}</div>
|
||||
|
@@ -58,8 +58,8 @@
|
||||
}
|
||||
|
||||
.body {
|
||||
flex-grow: 1;
|
||||
max-width: calc(100% - 40px);
|
||||
flex: 1;
|
||||
max-width: calc(100% - 80px);
|
||||
|
||||
.date {
|
||||
font-size: 12px;
|
||||
@@ -71,6 +71,12 @@
|
||||
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 { Updater } from "../typing";
|
||||
import { IconButton } from "./button";
|
||||
@@ -73,11 +73,23 @@ export function MessageSelector(props: {
|
||||
const chatStore = useChatStore();
|
||||
const session = chatStore.currentSession();
|
||||
const isValid = (m: ChatMessage) => m.content && !m.isError && !m.streaming;
|
||||
const messages = session.messages.filter(
|
||||
(m, i) =>
|
||||
m.id && // message must have id
|
||||
isValid(m) &&
|
||||
(i >= session.messages.length - 1 || isValid(session.messages[i + 1])),
|
||||
const allMessages = useMemo(() => {
|
||||
let startIndex = Math.max(0, session.clearContextIndex ?? 0);
|
||||
if (startIndex === session.messages.length - 1) {
|
||||
startIndex = 0;
|
||||
}
|
||||
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 config = useAppConfig();
|
||||
@@ -176,6 +188,8 @@ export function MessageSelector(props: {
|
||||
<div className={styles["messages"]}>
|
||||
{messages.map((m, i) => {
|
||||
if (!isInSearchResult(m.id!)) return null;
|
||||
const id = m.id ?? i;
|
||||
const isSelected = props.selection.has(id);
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -185,7 +199,6 @@ export function MessageSelector(props: {
|
||||
key={i}
|
||||
onClick={() => {
|
||||
props.updateSelection((selection) => {
|
||||
const id = m.id ?? i;
|
||||
selection.has(id) ? selection.delete(id) : selection.add(id);
|
||||
});
|
||||
onClickIndex(i);
|
||||
@@ -195,7 +208,10 @@ export function MessageSelector(props: {
|
||||
{m.role === "user" ? (
|
||||
<Avatar avatar={config.avatar}></Avatar>
|
||||
) : (
|
||||
<MaskAvatar mask={session.mask} />
|
||||
<MaskAvatar
|
||||
avatar={session.mask.avatar}
|
||||
model={m.model || session.mask.modelConfig.model}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className={styles["body"]}>
|
||||
@@ -206,6 +222,10 @@ export function MessageSelector(props: {
|
||||
{m.content}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles["checkbox"]}>
|
||||
<input type="checkbox" checked={isSelected}></input>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
@@ -1,14 +1,15 @@
|
||||
import { ModalConfigValidator, ModelConfig, useAppConfig } from "../store";
|
||||
import { ModalConfigValidator, ModelConfig } from "../store";
|
||||
|
||||
import Locale from "../locales";
|
||||
import { InputRange } from "./input-range";
|
||||
import { ListItem, Select } from "./ui-lib";
|
||||
import { useAllModels } from "../utils/hooks";
|
||||
|
||||
export function ModelConfigList(props: {
|
||||
modelConfig: ModelConfig;
|
||||
updateConfig: (updater: (config: ModelConfig) => void) => void;
|
||||
}) {
|
||||
const config = useAppConfig();
|
||||
const allModels = useAllModels();
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -24,11 +25,13 @@ export function ModelConfigList(props: {
|
||||
);
|
||||
}}
|
||||
>
|
||||
{config.allModels().map((v, i) => (
|
||||
<option value={v.name} key={i} disabled={!v.available}>
|
||||
{v.name}
|
||||
</option>
|
||||
))}
|
||||
{allModels
|
||||
.filter((v) => v.available)
|
||||
.map((v, i) => (
|
||||
<option value={v.name} key={i}>
|
||||
{v.displayName}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
</ListItem>
|
||||
<ListItem
|
||||
@@ -75,8 +78,8 @@ export function ModelConfigList(props: {
|
||||
>
|
||||
<input
|
||||
type="number"
|
||||
min={100}
|
||||
max={100000}
|
||||
min={1024}
|
||||
max={512000}
|
||||
value={props.modelConfig.max_tokens}
|
||||
onChange={(e) =>
|
||||
props.updateConfig(
|
||||
|
@@ -17,21 +17,13 @@ import { useCommand } from "../command";
|
||||
import { showConfirm } from "./ui-lib";
|
||||
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 }) {
|
||||
return (
|
||||
<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>
|
||||
);
|
||||
|
@@ -51,10 +51,13 @@ import Locale, {
|
||||
import { copyToClipboard } from "../utils";
|
||||
import Link from "next/link";
|
||||
import {
|
||||
Azure,
|
||||
OPENAI_BASE_URL,
|
||||
Path,
|
||||
RELEASE_URL,
|
||||
STORAGE_KEY,
|
||||
ServiceProvider,
|
||||
SlotID,
|
||||
UPDATE_URL,
|
||||
} from "../constant";
|
||||
import { Prompt, SearchService, usePromptStore } from "../store/prompt";
|
||||
@@ -580,8 +583,16 @@ export function Settings() {
|
||||
const accessStore = useAccessStore();
|
||||
const shouldHideBalanceQuery = useMemo(() => {
|
||||
const isOpenAiUrl = accessStore.openaiUrl.includes(OPENAI_BASE_URL);
|
||||
return accessStore.hideBalanceQuery || isOpenAiUrl;
|
||||
}, [accessStore.hideBalanceQuery, accessStore.openaiUrl]);
|
||||
return (
|
||||
accessStore.hideBalanceQuery ||
|
||||
isOpenAiUrl ||
|
||||
accessStore.provider === ServiceProvider.Azure
|
||||
);
|
||||
}, [
|
||||
accessStore.hideBalanceQuery,
|
||||
accessStore.openaiUrl,
|
||||
accessStore.provider,
|
||||
]);
|
||||
|
||||
const usage = {
|
||||
used: updateStore.used,
|
||||
@@ -624,6 +635,11 @@ export function Settings() {
|
||||
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);
|
||||
return () => {
|
||||
document.removeEventListener("keydown", keydownEvent);
|
||||
@@ -877,16 +893,16 @@ export function Settings() {
|
||||
</ListItem>
|
||||
</List>
|
||||
|
||||
<List>
|
||||
{showAccessCode ? (
|
||||
<List id={SlotID.CustomModel}>
|
||||
{showAccessCode && (
|
||||
<ListItem
|
||||
title={Locale.Settings.AccessCode.Title}
|
||||
subTitle={Locale.Settings.AccessCode.SubTitle}
|
||||
title={Locale.Settings.Access.AccessCode.Title}
|
||||
subTitle={Locale.Settings.Access.AccessCode.SubTitle}
|
||||
>
|
||||
<PasswordInput
|
||||
value={accessStore.accessCode}
|
||||
type="text"
|
||||
placeholder={Locale.Settings.AccessCode.Placeholder}
|
||||
placeholder={Locale.Settings.Access.AccessCode.Placeholder}
|
||||
onChange={(e) => {
|
||||
accessStore.update(
|
||||
(access) => (access.accessCode = e.currentTarget.value),
|
||||
@@ -894,46 +910,159 @@ export function Settings() {
|
||||
}}
|
||||
/>
|
||||
</ListItem>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
|
||||
{!accessStore.hideUserApiKey ? (
|
||||
{!accessStore.hideUserApiKey && (
|
||||
<>
|
||||
<ListItem
|
||||
title={Locale.Settings.Endpoint.Title}
|
||||
subTitle={Locale.Settings.Endpoint.SubTitle}
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
value={accessStore.openaiUrl}
|
||||
placeholder="https://api.openai.com/"
|
||||
onChange={(e) =>
|
||||
accessStore.update(
|
||||
(access) => (access.openaiUrl = e.currentTarget.value),
|
||||
)
|
||||
}
|
||||
></input>
|
||||
</ListItem>
|
||||
<ListItem
|
||||
title={Locale.Settings.Token.Title}
|
||||
subTitle={Locale.Settings.Token.SubTitle}
|
||||
>
|
||||
<PasswordInput
|
||||
value={accessStore.token}
|
||||
type="text"
|
||||
placeholder={Locale.Settings.Token.Placeholder}
|
||||
onChange={(e) => {
|
||||
accessStore.update(
|
||||
(access) => (access.token = e.currentTarget.value),
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</ListItem>
|
||||
</>
|
||||
) : null}
|
||||
{
|
||||
// Conditionally render the following ListItem based on clientConfig.isApp
|
||||
!clientConfig?.isApp && ( // only show if isApp is false
|
||||
<ListItem
|
||||
title={Locale.Settings.Access.CustomEndpoint.Title}
|
||||
subTitle={Locale.Settings.Access.CustomEndpoint.SubTitle}
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={accessStore.useCustomConfig}
|
||||
onChange={(e) =>
|
||||
accessStore.update(
|
||||
(access) =>
|
||||
(access.useCustomConfig = e.currentTarget.checked),
|
||||
)
|
||||
}
|
||||
></input>
|
||||
</ListItem>
|
||||
)
|
||||
}
|
||||
{accessStore.useCustomConfig && (
|
||||
<>
|
||||
<ListItem
|
||||
title={Locale.Settings.Access.Provider.Title}
|
||||
subTitle={Locale.Settings.Access.Provider.SubTitle}
|
||||
>
|
||||
<Select
|
||||
value={accessStore.provider}
|
||||
onChange={(e) => {
|
||||
accessStore.update(
|
||||
(access) =>
|
||||
(access.provider = e.target
|
||||
.value as ServiceProvider),
|
||||
);
|
||||
}}
|
||||
>
|
||||
{Object.entries(ServiceProvider).map(([k, v]) => (
|
||||
<option value={v} key={k}>
|
||||
{k}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
</ListItem>
|
||||
|
||||
{!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
|
||||
title={Locale.Settings.Usage.Title}
|
||||
subTitle={
|
||||
@@ -960,8 +1089,8 @@ export function Settings() {
|
||||
) : null}
|
||||
|
||||
<ListItem
|
||||
title={Locale.Settings.CustomModel.Title}
|
||||
subTitle={Locale.Settings.CustomModel.SubTitle}
|
||||
title={Locale.Settings.Access.CustomModel.Title}
|
||||
subTitle={Locale.Settings.Access.CustomModel.SubTitle}
|
||||
>
|
||||
<input
|
||||
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";
|
||||
|
||||
@@ -8,6 +8,7 @@ import GithubIcon from "../icons/github.svg";
|
||||
import ChatGptIcon from "../icons/chatgpt.svg";
|
||||
import AddIcon from "../icons/add.svg";
|
||||
import CloseIcon from "../icons/close.svg";
|
||||
import DeleteIcon from "../icons/delete.svg";
|
||||
import MaskIcon from "../icons/mask.svg";
|
||||
import PluginIcon from "../icons/plugin.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-action"] + " " + styles.mobile}>
|
||||
<IconButton
|
||||
icon={<CloseIcon />}
|
||||
icon={<DeleteIcon />}
|
||||
onClick={async () => {
|
||||
if (await showConfirm(Locale.Home.DeleteChat)) {
|
||||
chatStore.deleteSession(chatStore.currentSessionIndex);
|
||||
|
@@ -235,7 +235,7 @@
|
||||
.select-with-icon-select {
|
||||
height: 100%;
|
||||
border: var(--border-in-light);
|
||||
padding: 10px 25px 10px 10px;
|
||||
padding: 10px 35px 10px 10px;
|
||||
border-radius: 10px;
|
||||
appearance: none;
|
||||
cursor: pointer;
|
||||
|
@@ -70,14 +70,12 @@ export function ListItem(props: {
|
||||
);
|
||||
}
|
||||
|
||||
export function List(props: {
|
||||
children:
|
||||
| Array<JSX.Element | null | undefined>
|
||||
| JSX.Element
|
||||
| null
|
||||
| undefined;
|
||||
}) {
|
||||
return <div className={styles.list}>{props.children}</div>;
|
||||
export function List(props: { children: React.ReactNode; id?: string }) {
|
||||
return (
|
||||
<div className={styles.list} id={props.id}>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function Loading() {
|
||||
@@ -99,8 +97,9 @@ export function Loading() {
|
||||
interface ModalProps {
|
||||
title: string;
|
||||
children?: any;
|
||||
actions?: JSX.Element[];
|
||||
actions?: React.ReactNode[];
|
||||
defaultMax?: boolean;
|
||||
footer?: React.ReactNode;
|
||||
onClose?: () => void;
|
||||
}
|
||||
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-footer"]}>
|
||||
{props.footer}
|
||||
<div className={styles["modal-actions"]}>
|
||||
{props.actions?.map((action, i) => (
|
||||
<div key={i} className={styles["modal-action"]}>
|
||||
|
@@ -1,19 +1,31 @@
|
||||
import md5 from "spark-md5";
|
||||
import { DEFAULT_MODELS } from "../constant";
|
||||
|
||||
declare global {
|
||||
namespace NodeJS {
|
||||
interface ProcessEnv {
|
||||
PROXY_URL?: string; // docker only
|
||||
|
||||
OPENAI_API_KEY?: string;
|
||||
CODE?: string;
|
||||
|
||||
BASE_URL?: string;
|
||||
PROXY_URL?: string;
|
||||
OPENAI_ORG_ID?: string; // openai only
|
||||
|
||||
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_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
|
||||
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 {
|
||||
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,
|
||||
codes: ACCESS_CODES,
|
||||
needCode: ACCESS_CODES.size > 0,
|
||||
baseUrl: process.env.BASE_URL,
|
||||
|
||||
proxyUrl: process.env.PROXY_URL,
|
||||
isVercel: !!process.env.VERCEL,
|
||||
|
||||
hideUserApiKey: !!process.env.HIDE_USER_API_KEY,
|
||||
disableGPT4: !!process.env.DISABLE_GPT4,
|
||||
disableGPT4,
|
||||
hideBalanceQuery: !process.env.ENABLE_BALANCE_QUERY,
|
||||
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 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 OPENAI_BASE_URL = "https://api.openai.com";
|
||||
|
||||
@@ -23,10 +23,12 @@ export enum Path {
|
||||
|
||||
export enum ApiPath {
|
||||
Cors = "/api/cors",
|
||||
OpenAI = "/api/openai",
|
||||
}
|
||||
|
||||
export enum SlotID {
|
||||
AppBody = "app-body",
|
||||
CustomModel = "custom-model",
|
||||
}
|
||||
|
||||
export enum FileName {
|
||||
@@ -60,6 +62,11 @@ export const REQUEST_TIMEOUT_MS = 60000;
|
||||
|
||||
export const EXPORT_MESSAGE_CLASS_NAME = "export-markdown";
|
||||
|
||||
export enum ServiceProvider {
|
||||
OpenAI = "OpenAI",
|
||||
Azure = "Azure",
|
||||
}
|
||||
|
||||
export const OpenaiPath = {
|
||||
ChatPath: "v1/chat/completions",
|
||||
UsagePath: "dashboard/billing/usage",
|
||||
@@ -67,19 +74,24 @@ export const OpenaiPath = {
|
||||
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_SYSTEM_TEMPLATE = `
|
||||
You are ChatGPT, a large language model trained by OpenAI.
|
||||
Knowledge cutoff: {{cutoff}}
|
||||
Current model: {{model}}
|
||||
Current time: {{time}}
|
||||
Latex inline: $x^2$
|
||||
Latex block: $$e=mc^2$$
|
||||
`;
|
||||
|
||||
export const SUMMARIZE_MODEL = "gpt-3.5-turbo";
|
||||
|
||||
export const KnowledgeCutOffDate: Record<string, string> = {
|
||||
default: "2021-09",
|
||||
"gpt-3.5-turbo-1106": "2023-04",
|
||||
"gpt-4-1106-preview": "2023-04",
|
||||
"gpt-4-vision-preview": "2023-04",
|
||||
};
|
||||
|
@@ -167,11 +167,7 @@ ${builtin} مدمجة، ${custom} تم تعريفها من قبل المستخد
|
||||
Title: "حد الضغط للتاريخ",
|
||||
SubTitle: "سيتم الضغط إذا تجاوزت طول الرسائل غير المضغوطة الحد المحدد",
|
||||
},
|
||||
Token: {
|
||||
Title: "مفتاح API",
|
||||
SubTitle: "استخدم مفتاحك لتجاوز حد رمز الوصول",
|
||||
Placeholder: "مفتاح OpenAI API",
|
||||
},
|
||||
|
||||
Usage: {
|
||||
Title: "رصيد الحساب",
|
||||
SubTitle(used: any, total: any) {
|
||||
@@ -181,15 +177,7 @@ ${builtin} مدمجة، ${custom} تم تعريفها من قبل المستخد
|
||||
Check: "التحقق",
|
||||
NoAccess: "أدخل مفتاح API للتحقق من الرصيد",
|
||||
},
|
||||
AccessCode: {
|
||||
Title: "رمز الوصول",
|
||||
SubTitle: "تم تمكين التحكم في الوصول",
|
||||
Placeholder: "رمز الوصول المطلوب",
|
||||
},
|
||||
Endpoint: {
|
||||
Title: "نقطة النهاية",
|
||||
SubTitle: "يجب أن تبدأ نقطة النهاية المخصصة بـ http(s)://",
|
||||
},
|
||||
|
||||
Model: "النموذج",
|
||||
Temperature: {
|
||||
Title: "الحرارة",
|
||||
|
@@ -199,11 +199,7 @@ const bn: PartialLocaleType = {
|
||||
SubTitle:
|
||||
"নকুল বার্তা দৈর্ঘ্য সীমা অতিক্রান্ত হলে ঐ বার্তাটি সঙ্কুচিত হবে",
|
||||
},
|
||||
Token: {
|
||||
Title: "অ্যাপি কী",
|
||||
SubTitle: "অ্যাক্সেস কোড সীমা উপেক্ষা করতে আপনার কীটি ব্যবহার করুন",
|
||||
Placeholder: "OpenAI API কী",
|
||||
},
|
||||
|
||||
Usage: {
|
||||
Title: "একাউন্ট ব্যালেন্স",
|
||||
SubTitle(used: any, total: any) {
|
||||
@@ -213,15 +209,7 @@ const bn: PartialLocaleType = {
|
||||
Check: "চেক",
|
||||
NoAccess: "ব্যালেন্স চেক করতে অ্যাপি কী ইনপুট করুন",
|
||||
},
|
||||
AccessCode: {
|
||||
Title: "অ্যাক্সেস কোড",
|
||||
SubTitle: "অ্যাক্সেস নিয়ন্ত্রণ সক্রিয়",
|
||||
Placeholder: "অ্যাক্সেস কোড প্রয়োজন",
|
||||
},
|
||||
Endpoint: {
|
||||
Title: "ইনটারপয়েন্ট",
|
||||
SubTitle: "কাস্টম এন্ডপয়েন্টটি হতে হবে http(s):// দিয়ে শুরু হতে হবে",
|
||||
},
|
||||
|
||||
Model: "মডেল",
|
||||
Temperature: {
|
||||
Title: "তাপমাত্রা",
|
||||
|
@@ -85,8 +85,8 @@ const cn = {
|
||||
Copy: "全部复制",
|
||||
Download: "下载文件",
|
||||
Share: "分享到 ShareGPT",
|
||||
MessageFromYou: "来自你的消息",
|
||||
MessageFromChatGPT: "来自 ChatGPT 的消息",
|
||||
MessageFromYou: "用户",
|
||||
MessageFromChatGPT: "ChatGPT",
|
||||
Format: {
|
||||
Title: "导出格式",
|
||||
SubTitle: "可以导出 Markdown 文本或者 PNG 图片",
|
||||
@@ -258,11 +258,6 @@ const cn = {
|
||||
Title: "历史消息长度压缩阈值",
|
||||
SubTitle: "当未压缩的历史消息超过该值时,将进行压缩",
|
||||
},
|
||||
Token: {
|
||||
Title: "API Key",
|
||||
SubTitle: "使用自己的 Key 可绕过密码访问限制",
|
||||
Placeholder: "OpenAI API Key",
|
||||
},
|
||||
|
||||
Usage: {
|
||||
Title: "余额查询",
|
||||
@@ -273,19 +268,56 @@ const cn = {
|
||||
Check: "重新检查",
|
||||
NoAccess: "输入 API Key 或访问密码查看余额",
|
||||
},
|
||||
AccessCode: {
|
||||
Title: "访问密码",
|
||||
SubTitle: "管理员已开启加密访问",
|
||||
Placeholder: "请输入访问密码",
|
||||
},
|
||||
Endpoint: {
|
||||
Title: "接口地址",
|
||||
SubTitle: "除默认地址外,必须包含 http(s)://",
|
||||
},
|
||||
CustomModel: {
|
||||
Title: "自定义模型名",
|
||||
SubTitle: "增加自定义模型可选项,使用英文逗号隔开",
|
||||
|
||||
Access: {
|
||||
AccessCode: {
|
||||
Title: "访问密码",
|
||||
SubTitle: "管理员已开启加密访问",
|
||||
Placeholder: "请输入访问密码",
|
||||
},
|
||||
CustomEndpoint: {
|
||||
Title: "自定义接口",
|
||||
SubTitle: "是否使用自定义 Azure 或 OpenAI 服务",
|
||||
},
|
||||
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)",
|
||||
Temperature: {
|
||||
Title: "随机性 (temperature)",
|
||||
@@ -409,6 +441,9 @@ const cn = {
|
||||
Config: "配置",
|
||||
},
|
||||
Exporter: {
|
||||
Description : {
|
||||
Title: "只有清除上下文之后的消息会被展示"
|
||||
},
|
||||
Model: "模型",
|
||||
Messages: "消息",
|
||||
Topic: "主题",
|
||||
|
@@ -124,11 +124,7 @@ const cs: PartialLocaleType = {
|
||||
SubTitle:
|
||||
"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: {
|
||||
Title: "Stav účtu",
|
||||
SubTitle(used: any, total: any) {
|
||||
@@ -138,11 +134,7 @@ const cs: PartialLocaleType = {
|
||||
Check: "Zkontrolovat",
|
||||
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",
|
||||
Temperature: {
|
||||
Title: "Teplota",
|
||||
|
@@ -124,12 +124,7 @@ const de: PartialLocaleType = {
|
||||
SubTitle:
|
||||
"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: {
|
||||
Title: "Kontostand",
|
||||
SubTitle(used: any, total: any) {
|
||||
@@ -139,11 +134,6 @@ const de: PartialLocaleType = {
|
||||
Check: "Erneut prüfen",
|
||||
NoAccess: "API-Schlüssel eingeben, um den Kontostand zu überprüfen",
|
||||
},
|
||||
AccessCode: {
|
||||
Title: "Zugangscode",
|
||||
SubTitle: "Zugangskontrolle aktiviert",
|
||||
Placeholder: "Zugangscode erforderlich",
|
||||
},
|
||||
Model: "Modell",
|
||||
Temperature: {
|
||||
Title: "Temperature", //Temperatur
|
||||
|
@@ -262,11 +262,7 @@ const en: LocaleType = {
|
||||
SubTitle:
|
||||
"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: {
|
||||
Title: "Account Balance",
|
||||
SubTitle(used: any, total: any) {
|
||||
@@ -276,19 +272,55 @@ const en: LocaleType = {
|
||||
Check: "Check",
|
||||
NoAccess: "Enter API Key to check balance",
|
||||
},
|
||||
AccessCode: {
|
||||
Title: "Access Code",
|
||||
SubTitle: "Access control enabled",
|
||||
Placeholder: "Need Access Code",
|
||||
},
|
||||
Endpoint: {
|
||||
Title: "Endpoint",
|
||||
SubTitle: "Custom endpoint must start with http(s)://",
|
||||
},
|
||||
CustomModel: {
|
||||
Title: "Custom Models",
|
||||
SubTitle: "Add extra model options, separate by comma",
|
||||
Access: {
|
||||
AccessCode: {
|
||||
Title: "Access Code",
|
||||
SubTitle: "Access control Enabled",
|
||||
Placeholder: "Enter Code",
|
||||
},
|
||||
CustomEndpoint: {
|
||||
Title: "Custom Endpoint",
|
||||
SubTitle: "Use custom Azure or OpenAI service",
|
||||
},
|
||||
Provider: {
|
||||
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",
|
||||
Temperature: {
|
||||
Title: "Temperature",
|
||||
@@ -410,6 +442,9 @@ const en: LocaleType = {
|
||||
Config: "Config",
|
||||
},
|
||||
Exporter: {
|
||||
Description: {
|
||||
Title: "Only messages after clearing the context will be displayed"
|
||||
},
|
||||
Model: "Model",
|
||||
Messages: "Messages",
|
||||
Topic: "Topic",
|
||||
|
@@ -124,11 +124,7 @@ const es: PartialLocaleType = {
|
||||
SubTitle:
|
||||
"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: {
|
||||
Title: "Saldo de la cuenta",
|
||||
SubTitle(used: any, total: any) {
|
||||
@@ -138,11 +134,7 @@ const es: PartialLocaleType = {
|
||||
Check: "Comprobar de nuevo",
|
||||
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",
|
||||
Temperature: {
|
||||
Title: "Temperatura",
|
||||
|
@@ -173,11 +173,7 @@ const fr: PartialLocaleType = {
|
||||
SubTitle:
|
||||
"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: {
|
||||
Title: "Solde du compte",
|
||||
SubTitle(used: any, total: any) {
|
||||
@@ -187,11 +183,7 @@ const fr: PartialLocaleType = {
|
||||
Check: "Vérifier",
|
||||
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",
|
||||
Temperature: {
|
||||
Title: "Température",
|
||||
|
@@ -4,8 +4,9 @@ import { PartialLocaleType } from "./index";
|
||||
const id: PartialLocaleType = {
|
||||
WIP: "Coming Soon...",
|
||||
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: {
|
||||
Title: "Diperlukan Kode Akses",
|
||||
Tips: "Masukkan kode akses di bawah",
|
||||
@@ -237,11 +238,7 @@ const id: PartialLocaleType = {
|
||||
SubTitle:
|
||||
"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: {
|
||||
Title: "Saldo Akun",
|
||||
SubTitle(used: any, total: any) {
|
||||
@@ -251,15 +248,7 @@ const id: PartialLocaleType = {
|
||||
Check: "Periksa",
|
||||
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",
|
||||
Temperature: {
|
||||
Title: "Suhu",
|
||||
@@ -379,6 +368,9 @@ const id: PartialLocaleType = {
|
||||
Edit: "Edit",
|
||||
},
|
||||
Exporter: {
|
||||
Description: {
|
||||
Title: "Hanya pesan setelah menghapus konteks yang akan ditampilkan"
|
||||
},
|
||||
Model: "Model",
|
||||
Messages: "Pesan",
|
||||
Topic: "Topik",
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import cn from "./cn";
|
||||
import en from "./en";
|
||||
import pt from "./pt";
|
||||
import tw from "./tw";
|
||||
import id from "./id";
|
||||
import fr from "./fr";
|
||||
@@ -24,6 +25,7 @@ const ALL_LANGS = {
|
||||
cn,
|
||||
en,
|
||||
tw,
|
||||
pt,
|
||||
jp,
|
||||
ko,
|
||||
id,
|
||||
@@ -47,6 +49,7 @@ export const AllLangs = Object.keys(ALL_LANGS) as Lang[];
|
||||
export const ALL_LANG_OPTIONS: Record<Lang, string> = {
|
||||
cn: "简体中文",
|
||||
en: "English",
|
||||
pt: "Português",
|
||||
tw: "繁體中文",
|
||||
jp: "日本語",
|
||||
ko: "한국어",
|
||||
|
@@ -124,12 +124,7 @@ const it: PartialLocaleType = {
|
||||
SubTitle:
|
||||
"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: {
|
||||
Title: "Bilancio Account",
|
||||
SubTitle(used: any, total: any) {
|
||||
@@ -139,11 +134,7 @@ const it: PartialLocaleType = {
|
||||
Check: "Controlla ancora",
|
||||
NoAccess: "Inserire la chiave API per controllare il saldo",
|
||||
},
|
||||
AccessCode: {
|
||||
Title: "Codice d'accesso",
|
||||
SubTitle: "Controllo d'accesso abilitato",
|
||||
Placeholder: "Inserisci il codice d'accesso",
|
||||
},
|
||||
|
||||
Model: "Modello GPT",
|
||||
Temperature: {
|
||||
Title: "Temperature",
|
||||
|
@@ -20,7 +20,8 @@ const jp: PartialLocaleType = {
|
||||
Stop: "停止",
|
||||
Retry: "リトライ",
|
||||
Pin: "ピン",
|
||||
PinToastContent: "コンテキストプロンプトに1つのメッセージをピン留めしました",
|
||||
PinToastContent:
|
||||
"コンテキストプロンプトに1つのメッセージをピン留めしました",
|
||||
PinToastAction: "表示",
|
||||
Delete: "削除",
|
||||
Edit: "編集",
|
||||
@@ -146,11 +147,7 @@ const jp: PartialLocaleType = {
|
||||
SubTitle:
|
||||
"圧縮されていない履歴メッセージがこの値を超えた場合、圧縮が行われます。",
|
||||
},
|
||||
Token: {
|
||||
Title: "APIキー",
|
||||
SubTitle: "自分のキーを使用してパスワードアクセス制限を迂回する",
|
||||
Placeholder: "OpenAI APIキー",
|
||||
},
|
||||
|
||||
Usage: {
|
||||
Title: "残高照会",
|
||||
SubTitle(used: any, total: any) {
|
||||
@@ -160,11 +157,7 @@ const jp: PartialLocaleType = {
|
||||
Check: "再確認",
|
||||
NoAccess: "APIキーまたはアクセスパスワードを入力して残高を表示",
|
||||
},
|
||||
AccessCode: {
|
||||
Title: "アクセスパスワード",
|
||||
SubTitle: "暗号化アクセスが有効になっています",
|
||||
Placeholder: "アクセスパスワードを入力してください",
|
||||
},
|
||||
|
||||
Model: "モデル (model)",
|
||||
Temperature: {
|
||||
Title: "ランダム性 (temperature)",
|
||||
|
@@ -124,11 +124,7 @@ const ko: PartialLocaleType = {
|
||||
Title: "기록 압축 임계값",
|
||||
SubTitle: "미압축 메시지 길이가 임계값을 초과하면 압축됨",
|
||||
},
|
||||
Token: {
|
||||
Title: "API 키",
|
||||
SubTitle: "액세스 코드 제한을 무시하기 위해 키 사용",
|
||||
Placeholder: "OpenAI API 키",
|
||||
},
|
||||
|
||||
Usage: {
|
||||
Title: "계정 잔액",
|
||||
SubTitle(used: any, total: any) {
|
||||
@@ -138,11 +134,7 @@ const ko: PartialLocaleType = {
|
||||
Check: "확인",
|
||||
NoAccess: "잔액 확인을 위해 API 키를 입력하세요.",
|
||||
},
|
||||
AccessCode: {
|
||||
Title: "액세스 코드",
|
||||
SubTitle: "액세스 제어가 활성화됨",
|
||||
Placeholder: "액세스 코드 입력",
|
||||
},
|
||||
|
||||
Model: "모델",
|
||||
Temperature: {
|
||||
Title: "온도 (temperature)",
|
||||
|
@@ -106,12 +106,7 @@ const no: PartialLocaleType = {
|
||||
SubTitle:
|
||||
"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: {
|
||||
Title: "Saldo for konto",
|
||||
SubTitle(used: any, total: any) {
|
||||
@@ -121,11 +116,7 @@ const no: PartialLocaleType = {
|
||||
Check: "Sjekk",
|
||||
NoAccess: "Skriv inn API-nøkkelen for å sjekke saldo",
|
||||
},
|
||||
AccessCode: {
|
||||
Title: "Tilgangskode",
|
||||
SubTitle: "Tilgangskontroll på",
|
||||
Placeholder: "Trenger tilgangskode",
|
||||
},
|
||||
|
||||
Model: "Model",
|
||||
Temperature: {
|
||||
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:
|
||||
"Будет сжимать, если длина несжатых сообщений превышает указанное значение",
|
||||
},
|
||||
Token: {
|
||||
Title: "API ключ",
|
||||
SubTitle: "Используйте свой ключ, чтобы игнорировать лимит доступа",
|
||||
Placeholder: "API ключ OpenAI",
|
||||
},
|
||||
|
||||
Usage: {
|
||||
Title: "Баланс аккаунта",
|
||||
SubTitle(used: any, total: any) {
|
||||
@@ -139,11 +135,7 @@ const ru: PartialLocaleType = {
|
||||
Check: "Проверить",
|
||||
NoAccess: "Введите API ключ, чтобы проверить баланс",
|
||||
},
|
||||
AccessCode: {
|
||||
Title: "Код доступа",
|
||||
SubTitle: "Контроль доступа включен",
|
||||
Placeholder: "Требуется код доступа",
|
||||
},
|
||||
|
||||
Model: "Модель",
|
||||
Temperature: {
|
||||
Title: "Температура",
|
||||
|
@@ -124,11 +124,7 @@ const tr: PartialLocaleType = {
|
||||
SubTitle:
|
||||
"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: {
|
||||
Title: "Hesap Bakiyesi",
|
||||
SubTitle(used: any, total: any) {
|
||||
@@ -138,11 +134,7 @@ const tr: PartialLocaleType = {
|
||||
Check: "Tekrar Kontrol Et",
|
||||
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",
|
||||
Temperature: {
|
||||
Title: "Gerçeklik",
|
||||
|
@@ -120,11 +120,7 @@ const tw: PartialLocaleType = {
|
||||
Title: "歷史訊息長度壓縮閾值",
|
||||
SubTitle: "當未壓縮的歷史訊息超過該值時,將進行壓縮",
|
||||
},
|
||||
Token: {
|
||||
Title: "API Key",
|
||||
SubTitle: "使用自己的 Key 可規避授權存取限制",
|
||||
Placeholder: "OpenAI API Key",
|
||||
},
|
||||
|
||||
Usage: {
|
||||
Title: "帳戶餘額",
|
||||
SubTitle(used: any, total: any) {
|
||||
@@ -134,11 +130,7 @@ const tw: PartialLocaleType = {
|
||||
Check: "重新檢查",
|
||||
NoAccess: "輸入 API Key 檢視餘額",
|
||||
},
|
||||
AccessCode: {
|
||||
Title: "授權碼",
|
||||
SubTitle: "目前是未授權存取狀態",
|
||||
Placeholder: "請輸入授權碼",
|
||||
},
|
||||
|
||||
Model: "模型 (model)",
|
||||
Temperature: {
|
||||
Title: "隨機性 (temperature)",
|
||||
|
@@ -123,11 +123,7 @@ const vi: PartialLocaleType = {
|
||||
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",
|
||||
},
|
||||
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: {
|
||||
Title: "Hạn mức tài khoản",
|
||||
SubTitle(used: any, total: any) {
|
||||
@@ -137,11 +133,7 @@ const vi: PartialLocaleType = {
|
||||
Check: "Kiểm tra",
|
||||
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",
|
||||
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 { getClientConfig } from "../config/client";
|
||||
import { createPersistStore } from "../utils/store";
|
||||
import { ensure } from "../utils/clone";
|
||||
|
||||
let fetchState = 0; // 0 not fetch, 1 fetching, 2 done
|
||||
|
||||
const DEFAULT_OPENAI_URL =
|
||||
getClientConfig()?.buildMode === "export" ? DEFAULT_API_HOST : "/api/openai/";
|
||||
console.log("[API] default openai url", DEFAULT_OPENAI_URL);
|
||||
getClientConfig()?.buildMode === "export" ? DEFAULT_API_HOST : ApiPath.OpenAI;
|
||||
|
||||
const DEFAULT_ACCESS_STATE = {
|
||||
token: "",
|
||||
accessCode: "",
|
||||
useCustomConfig: false,
|
||||
|
||||
provider: ServiceProvider.OpenAI,
|
||||
|
||||
// openai
|
||||
openaiUrl: DEFAULT_OPENAI_URL,
|
||||
openaiApiKey: "",
|
||||
|
||||
// azure
|
||||
azureUrl: "",
|
||||
azureApiKey: "",
|
||||
azureApiVersion: "2023-08-01-preview",
|
||||
|
||||
// server config
|
||||
needCode: true,
|
||||
hideUserApiKey: false,
|
||||
hideBalanceQuery: false,
|
||||
disableGPT4: false,
|
||||
disableFastLink: false,
|
||||
|
||||
openaiUrl: DEFAULT_OPENAI_URL,
|
||||
customModels: "",
|
||||
};
|
||||
|
||||
export const useAccessStore = createPersistStore(
|
||||
@@ -30,12 +47,24 @@ export const useAccessStore = createPersistStore(
|
||||
|
||||
return get().needCode;
|
||||
},
|
||||
|
||||
isValidOpenAI() {
|
||||
return ensure(get(), ["openaiApiKey"]);
|
||||
},
|
||||
|
||||
isValidAzure() {
|
||||
return ensure(get(), ["azureUrl", "azureApiKey", "azureApiVersion"]);
|
||||
},
|
||||
|
||||
isAuthorized() {
|
||||
this.fetch();
|
||||
|
||||
// has token or has code or disabled access control
|
||||
return (
|
||||
!!get().token || !!get().accessCode || !this.enabledAccessControl()
|
||||
this.isValidOpenAI() ||
|
||||
this.isValidAzure() ||
|
||||
!this.enabledAccessControl() ||
|
||||
(this.enabledAccessControl() && ensure(get(), ["accessCode"]))
|
||||
);
|
||||
},
|
||||
fetch() {
|
||||
@@ -52,12 +81,6 @@ export const useAccessStore = createPersistStore(
|
||||
.then((res: DangerConfig) => {
|
||||
console.log("[Config] got config from server", res);
|
||||
set(() => ({ ...res }));
|
||||
|
||||
if (res.disableGPT4) {
|
||||
DEFAULT_MODELS.forEach(
|
||||
(m: any) => (m.available = !m.name.startsWith("gpt-4")),
|
||||
);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
console.error("[Config] failed to fetch config");
|
||||
@@ -69,6 +92,19 @@ export const useAccessStore = createPersistStore(
|
||||
}),
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
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[]) {
|
||||
return msgs.reduce((pre, cur) => pre + estimateTokenLength(cur.content), 0);
|
||||
}
|
||||
@@ -584,7 +557,10 @@ export const useChatStore = createPersistStore(
|
||||
},
|
||||
onFinish(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) {
|
||||
console.error("[Summarize] ", err);
|
||||
|
@@ -49,7 +49,7 @@ export const DEFAULT_CONFIG = {
|
||||
model: "gpt-3.5-turbo" as ModelType,
|
||||
temperature: 0.5,
|
||||
top_p: 1,
|
||||
max_tokens: 2000,
|
||||
max_tokens: 4000,
|
||||
presence_penalty: 0,
|
||||
frequency_penalty: 0,
|
||||
sendMemory: true,
|
||||
@@ -82,7 +82,7 @@ export const ModalConfigValidator = {
|
||||
return x as ModelType;
|
||||
},
|
||||
max_tokens(x: number) {
|
||||
return limitNumber(x, 0, 100000, 2000);
|
||||
return limitNumber(x, 0, 512000, 1024);
|
||||
},
|
||||
presence_penalty(x: number) {
|
||||
return limitNumber(x, -2, 2, 0);
|
||||
@@ -128,15 +128,7 @@ export const useAppConfig = createPersistStore(
|
||||
}));
|
||||
},
|
||||
|
||||
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;
|
||||
},
|
||||
allModels() {},
|
||||
}),
|
||||
{
|
||||
name: StoreKey.Config,
|
||||
|
@@ -357,3 +357,7 @@ pre {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.copyable {
|
||||
user-select: text;
|
||||
}
|
||||
|
@@ -3,7 +3,10 @@ import { showToast } from "./components/ui-lib";
|
||||
import Locale from "./locales";
|
||||
|
||||
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) {
|
||||
|
@@ -1,3 +1,12 @@
|
||||
export function deepClone<T>(obj: T) {
|
||||
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 { persist } from "zustand/middleware";
|
||||
import { combine, persist } from "zustand/middleware";
|
||||
import { Updater } from "../typing";
|
||||
import { deepClone } from "./clone";
|
||||
|
||||
@@ -23,33 +23,42 @@ type SetStoreState<T> = (
|
||||
replace?: boolean | undefined,
|
||||
) => void;
|
||||
|
||||
export function createPersistStore<T, M>(
|
||||
defaultState: T,
|
||||
export function createPersistStore<T extends object, M>(
|
||||
state: T,
|
||||
methods: (
|
||||
set: SetStoreState<T & MakeUpdater<T>>,
|
||||
get: () => T & MakeUpdater<T>,
|
||||
) => M,
|
||||
persistOptions: SecondParam<typeof persist<T & M & MakeUpdater<T>>>,
|
||||
) {
|
||||
return create<T & M & MakeUpdater<T>>()(
|
||||
persist((set, get) => {
|
||||
return {
|
||||
...defaultState,
|
||||
...methods(set as any, get),
|
||||
return create(
|
||||
persist(
|
||||
combine(
|
||||
{
|
||||
...state,
|
||||
lastUpdateTime: 0,
|
||||
},
|
||||
(set, get) => {
|
||||
return {
|
||||
...methods(set, get as any),
|
||||
|
||||
lastUpdateTime: 0,
|
||||
markUpdate() {
|
||||
set({ lastUpdateTime: Date.now() } as Partial<
|
||||
T & M & MakeUpdater<T>
|
||||
>);
|
||||
markUpdate() {
|
||||
set({ lastUpdateTime: Date.now() } as Partial<
|
||||
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());
|
||||
updater(state);
|
||||
get().markUpdate();
|
||||
set(state);
|
||||
},
|
||||
};
|
||||
}, persistOptions),
|
||||
),
|
||||
persistOptions as any,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
version: '3.9'
|
||||
version: "3.9"
|
||||
services:
|
||||
chatgpt-next-web:
|
||||
chatgpt-next-web:
|
||||
profiles: ["no-proxy"]
|
||||
container_name: chatgpt-next-web
|
||||
image: yidadaa/chatgpt-next-web
|
||||
@@ -13,8 +13,11 @@ services:
|
||||
- OPENAI_ORG_ID=$OPENAI_ORG_ID
|
||||
- HIDE_USER_API_KEY=$HIDE_USER_API_KEY
|
||||
- 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"]
|
||||
container_name: chatgpt-next-web-proxy
|
||||
image: yidadaa/chatgpt-next-web
|
||||
@@ -28,3 +31,6 @@ services:
|
||||
- OPENAI_ORG_ID=$OPENAI_ORG_ID
|
||||
- HIDE_USER_API_KEY=$HIDE_USER_API_KEY
|
||||
- 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 部署指南
|
||||
|
||||
## 如何新建项目
|
||||
|
||||
在 Github 上 fork 本项目,然后登录到 dash.cloudflare.com 并进入 Pages。
|
||||
|
||||
1. 点击 "Create a project"。
|
||||
@@ -12,7 +13,7 @@
|
||||
7. 在 "Build Settings" 中,选择 "Framework presets" 选项并选择 "Next.js"。
|
||||
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",使用默认值并且不要修改。
|
||||
10. 不要修改 "Root Directory"。
|
||||
@@ -30,10 +31,12 @@
|
||||
- `OPENAI_ORG_ID= 可选填,指定 OpenAI 中的组织 ID`
|
||||
- `HIDE_USER_API_KEY=1 可选,不让用户自行填入 API Key`
|
||||
- `DISABLE_GPT4=1 可选,不让用户使用 GPT-4`
|
||||
|
||||
- `ENABLE_BALANCE_QUERY=1 可选,启用余额查询功能`
|
||||
- `DISABLE_FAST_LINK=1 可选,禁用从链接解析预制设置`
|
||||
|
||||
12. 点击 "Save and Deploy"。
|
||||
13. 点击 "Cancel deployment",因为需要填写 Compatibility flags。
|
||||
14. 前往 "Build settings"、"Functions",找到 "Compatibility flags"。
|
||||
15. 在 "Configure Production compatibility flag" 和 "Configure Preview compatibility flag" 中填写 "nodejs_compat"。
|
||||
16. 前往 "Deployments",点击 "Retry deployment"。
|
||||
17. Enjoy.
|
||||
17. Enjoy.
|
||||
|
@@ -1,6 +1,7 @@
|
||||
# Cloudflare Pages Deployment Guide
|
||||
|
||||
## How to create a new project
|
||||
|
||||
Fork this project on GitHub, then log in to dash.cloudflare.com and go to Pages.
|
||||
|
||||
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.
|
||||
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:
|
||||
```
|
||||
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.
|
||||
10. Do not modify "Root Directory".
|
||||
11. For "Environment variables", click ">" and then "Add variable". Fill in the following information:
|
||||
|
||||
- `NODE_VERSION=20.1`
|
||||
- `NEXT_TELEMETRY_DISABLE=1`
|
||||
- `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`
|
||||
- `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`
|
||||
|
||||
- `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".
|
||||
13. Click "Cancel deployment" because you need to fill in 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",
|
||||
"@svgr/webpack": "^6.5.1",
|
||||
"@vercel/analytics": "^0.1.11",
|
||||
"emoji-picker-react": "^4.5.1",
|
||||
"emoji-picker-react": "^4.5.15",
|
||||
"fuse.js": "^6.6.2",
|
||||
"html-to-image": "^1.11.11",
|
||||
"mermaid": "^10.3.1",
|
||||
"nanoid": "^4.0.2",
|
||||
"mermaid": "^10.6.1",
|
||||
"nanoid": "^5.0.3",
|
||||
"next": "^13.4.9",
|
||||
"node-fetch": "^3.3.1",
|
||||
"react": "^18.2.0",
|
||||
@@ -43,11 +43,11 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tauri-apps/cli": "^1.4.0",
|
||||
"@types/node": "^20.3.3",
|
||||
"@types/node": "^20.9.0",
|
||||
"@types/react": "^18.2.14",
|
||||
"@types/react-dom": "^18.2.7",
|
||||
"@types/react-katex": "^3.0.0",
|
||||
"@types/spark-md5": "^3.0.2",
|
||||
"@types/spark-md5": "^3.0.4",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^8.49.0",
|
||||
"eslint-config-next": "13.4.19",
|
||||
|
@@ -9,7 +9,7 @@
|
||||
},
|
||||
"package": {
|
||||
"productName": "ChatGPT Next Web",
|
||||
"version": "2.9.10"
|
||||
"version": "2.9.13"
|
||||
},
|
||||
"tauri": {
|
||||
"allowlist": {
|
||||
|
21
vercel.json
21
vercel.json
@@ -1,24 +1,5 @@
|
||||
{
|
||||
"github": {
|
||||
"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"
|
||||
integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==
|
||||
|
||||
"@types/node@*", "@types/node@^20.3.3":
|
||||
version "20.3.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.3.3.tgz#329842940042d2b280897150e023e604d11657d6"
|
||||
integrity sha512-wheIYdr4NYML61AjC8MKj/2jrR/kDQri/CIpVoZwldwhnIrD/j9jIU5bJ8yBKuB2VhpFV7Ab6G2XkBjv9r9Zzw==
|
||||
"@types/node@*", "@types/node@^20.9.0":
|
||||
version "20.9.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.9.0.tgz#bfcdc230583aeb891cf51e73cfdaacdd8deae298"
|
||||
integrity sha512-nekiGu2NDb1BcVofVcEKMIwzlx4NjHlcjhoxxKBNLtz15Y1z7MYf549DFvkHSId02Ax6kGwWntIBPC3l/JZcmw==
|
||||
dependencies:
|
||||
undici-types "~5.26.4"
|
||||
|
||||
"@types/parse-json@^4.0.0":
|
||||
version "4.0.0"
|
||||
@@ -1550,10 +1552,10 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.3.tgz#cef09e3ec9af1d63d2a6cc5b383a737e24e6dcf5"
|
||||
integrity sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==
|
||||
|
||||
"@types/spark-md5@^3.0.2":
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/spark-md5/-/spark-md5-3.0.2.tgz#da2e8a778a20335fc4f40b6471c4b0d86b70da55"
|
||||
integrity sha512-82E/lVRaqelV9qmRzzJ1PKTpyrpnT7mwdneKNJB9hUtypZDMggloDfFUCIqRRx3lYRxteCwXSq9c+W71Vf0QnQ==
|
||||
"@types/spark-md5@^3.0.4":
|
||||
version "3.0.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/spark-md5/-/spark-md5-3.0.4.tgz#c1221d63c069d95aba0c06a765b80661cacc12bf"
|
||||
integrity sha512-qtOaDz+IXiNndPgYb6t1YoutnGvFRtWSNzpVjkAPCfB2UzTyybuD4Tjgs7VgRawum3JnJNRwNQd4N//SvrHg1Q==
|
||||
|
||||
"@types/unist@*", "@types/unist@^2.0.0":
|
||||
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"
|
||||
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:
|
||||
version "1.9.3"
|
||||
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"
|
||||
integrity sha512-L6uRgvZTH+4OF5NE/MBbzQx/WYpru1xCBE9respNj6qznEewGUIfhzmm7horWWxbNO2M0WckQypGctR8lH79xQ==
|
||||
|
||||
emoji-picker-react@^4.5.1:
|
||||
version "4.5.1"
|
||||
resolved "https://registry.yarnpkg.com/emoji-picker-react/-/emoji-picker-react-4.5.1.tgz#341f27dc86ad09340a316e0632484fcb9aff7195"
|
||||
integrity sha512-zpm0ui0TWkXZDUIevyNM0rC9Jyqc08RvVXH0KgsbSkDr+VgMQmYLu6UeI4SIWMZKsKMjQwujPpncRCFlEeykjw==
|
||||
dependencies:
|
||||
clsx "^1.2.1"
|
||||
emoji-picker-react@^4.5.15:
|
||||
version "4.5.15"
|
||||
resolved "https://registry.yarnpkg.com/emoji-picker-react/-/emoji-picker-react-4.5.15.tgz#e12797c50584cb8af8aee7eb6c7c8fd953e41f7e"
|
||||
integrity sha512-BTqo+pNUE8kqX8BKFTbD4fhlxcA69qfie5En4PerReLaaPfXVyRlDJ1uf85nKj2u5esUQ999iUf8YyqcPsM2Qw==
|
||||
|
||||
emoji-regex@^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"
|
||||
integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
|
||||
|
||||
mermaid@^10.3.1:
|
||||
version "10.3.1"
|
||||
resolved "https://registry.yarnpkg.com/mermaid/-/mermaid-10.3.1.tgz#2f3c7e9f6bd7a8da2bef71cce2a542c8eba2a62e"
|
||||
integrity sha512-hkenh7WkuRWPcob3oJtrN3W+yzrrIYuWF1OIfk/d0xGE8UWlvDhfexaHmDwwe8DKQgqMLI8DWEPwGprxkumjuw==
|
||||
mermaid@^10.6.1:
|
||||
version "10.6.1"
|
||||
resolved "https://registry.yarnpkg.com/mermaid/-/mermaid-10.6.1.tgz#701f4160484137a417770ce757ce1887a98c00fc"
|
||||
integrity sha512-Hky0/RpOw/1il9X8AvzOEChfJtVvmXm+y7JML5C//ePYMy0/9jCEmW1E1g86x9oDfW9+iVEdTV/i+M6KWRNs4A==
|
||||
dependencies:
|
||||
"@braintree/sanitize-url" "^6.0.1"
|
||||
"@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"
|
||||
integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==
|
||||
|
||||
nanoid@^4.0.2:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.npmmirror.com/nanoid/-/nanoid-4.0.2.tgz#140b3c5003959adbebf521c170f282c5e7f9fb9e"
|
||||
integrity sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==
|
||||
nanoid@^5.0.3:
|
||||
version "5.0.3"
|
||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-5.0.3.tgz#6c97f53d793a7a1de6a38ebb46f50f95bf9793c7"
|
||||
integrity sha512-I7X2b22cxA4LIHXPSqbBCEQSL+1wv8TuoefejsX4HFWyC6jc5JG7CEaxOltiKjc1M+YCS2YkrZZcj4+dytw9GA==
|
||||
|
||||
natural-compare@^1.4.0:
|
||||
version "1.4.0"
|
||||
@@ -5799,6 +5794,11 @@ unbox-primitive@^1.0.2:
|
||||
has-symbols "^1.0.3"
|
||||
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:
|
||||
version "2.0.0"
|
||||
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