diff --git a/README.md b/README.md
index d370000fa68..dd2c5b1eedc 100644
--- a/README.md
+++ b/README.md
@@ -31,7 +31,7 @@ One-Click to get a well-designed cross-platform ChatGPT web UI, with GPT3, GPT4
[MacOS-image]: https://img.shields.io/badge/-MacOS-black?logo=apple
[Linux-image]: https://img.shields.io/badge/-Linux-333?logo=ubuntu
-[](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FChatGPTNextWeb%2FChatGPT-Next-Web&env=OPENAI_API_KEY&env=CODE&project-name=nextchat&repository-name=NextChat) [](https://zeabur.com/templates/ZBUEFA) [](https://gitpod.io/#https://github.com/Yidadaa/ChatGPT-Next-Web)
+[](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FChatGPTNextWeb%2FChatGPT-Next-Web&env=OPENAI_API_KEY&env=CODE&project-name=nextchat&repository-name=NextChat) [](https://zeabur.com/templates/ZBUEFA) [](https://gitpod.io/#https://github.com/Yidadaa/ChatGPT-Next-Web) [](https://www.bt.cn/new/download.html) [](https://computenest.aliyun.com/market/service-f1c9b75e59814dc49d52)
[](https://monica.im/?utm=nxcrp)
@@ -397,6 +397,9 @@ yarn dev
> [简体中文 > 如何部署到私人服务器](./README_CN.md#部署)
+### BT Install
+> [简体中文 > 如何通过宝塔一键部署](./docs/bt-cn.md)
+
### Docker (Recommended)
```shell
diff --git a/README_CN.md b/README_CN.md
index 3f339ea61f6..ccdcf28ffe9 100644
--- a/README_CN.md
+++ b/README_CN.md
@@ -264,6 +264,9 @@ BASE_URL=https://b.nextweb.fun/api/proxy
## 部署
+### 宝塔面板部署
+> [简体中文 > 如何通过宝塔一键部署](./docs/bt-cn.md)
+
### 容器部署 (推荐)
> Docker 版本需要在 20 及其以上,否则会提示找不到镜像。
diff --git a/app/api/[provider]/[...path]/route.ts b/app/api/[provider]/[...path]/route.ts
index 5ac248d0c87..3017fd37180 100644
--- a/app/api/[provider]/[...path]/route.ts
+++ b/app/api/[provider]/[...path]/route.ts
@@ -11,6 +11,7 @@ import { handle as moonshotHandler } from "../../moonshot";
import { handle as stabilityHandler } from "../../stability";
import { handle as iflytekHandler } from "../../iflytek";
import { handle as xaiHandler } from "../../xai";
+import { handle as chatglmHandler } from "../../glm";
import { handle as proxyHandler } from "../../proxy";
async function handle(
@@ -41,6 +42,8 @@ async function handle(
return iflytekHandler(req, { params });
case ApiPath.XAI:
return xaiHandler(req, { params });
+ case ApiPath.ChatGLM:
+ return chatglmHandler(req, { params });
case ApiPath.OpenAI:
return openaiHandler(req, { params });
default:
diff --git a/app/api/auth.ts b/app/api/auth.ts
index d4ac66a113b..6703b64bd15 100644
--- a/app/api/auth.ts
+++ b/app/api/auth.ts
@@ -95,6 +95,9 @@ export function auth(req: NextRequest, modelProvider: ModelProvider) {
case ModelProvider.XAI:
systemApiKey = serverConfig.xaiApiKey;
break;
+ case ModelProvider.ChatGLM:
+ systemApiKey = serverConfig.chatglmApiKey;
+ break;
case ModelProvider.GPT:
default:
if (req.nextUrl.pathname.includes("azure/deployments")) {
diff --git a/app/api/glm.ts b/app/api/glm.ts
new file mode 100644
index 00000000000..3625b9f7bf9
--- /dev/null
+++ b/app/api/glm.ts
@@ -0,0 +1,129 @@
+import { getServerSideConfig } from "@/app/config/server";
+import {
+ CHATGLM_BASE_URL,
+ ApiPath,
+ ModelProvider,
+ ServiceProvider,
+} from "@/app/constant";
+import { prettyObject } from "@/app/utils/format";
+import { NextRequest, NextResponse } from "next/server";
+import { auth } from "@/app/api/auth";
+import { isModelAvailableInServer } from "@/app/utils/model";
+
+const serverConfig = getServerSideConfig();
+
+export async function handle(
+ req: NextRequest,
+ { params }: { params: { path: string[] } },
+) {
+ console.log("[GLM Route] params ", params);
+
+ if (req.method === "OPTIONS") {
+ return NextResponse.json({ body: "OK" }, { status: 200 });
+ }
+
+ const authResult = auth(req, ModelProvider.ChatGLM);
+ if (authResult.error) {
+ return NextResponse.json(authResult, {
+ status: 401,
+ });
+ }
+
+ try {
+ const response = await request(req);
+ return response;
+ } catch (e) {
+ console.error("[GLM] ", e);
+ return NextResponse.json(prettyObject(e));
+ }
+}
+
+async function request(req: NextRequest) {
+ const controller = new AbortController();
+
+ // alibaba use base url or just remove the path
+ let path = `${req.nextUrl.pathname}`.replaceAll(ApiPath.ChatGLM, "");
+
+ let baseUrl = serverConfig.chatglmUrl || CHATGLM_BASE_URL;
+
+ if (!baseUrl.startsWith("http")) {
+ baseUrl = `https://${baseUrl}`;
+ }
+
+ if (baseUrl.endsWith("/")) {
+ baseUrl = baseUrl.slice(0, -1);
+ }
+
+ console.log("[Proxy] ", path);
+ console.log("[Base Url]", baseUrl);
+
+ const timeoutId = setTimeout(
+ () => {
+ controller.abort();
+ },
+ 10 * 60 * 1000,
+ );
+
+ const fetchUrl = `${baseUrl}${path}`;
+ console.log("[Fetch Url] ", fetchUrl);
+ const fetchOptions: RequestInit = {
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: req.headers.get("Authorization") ?? "",
+ },
+ method: req.method,
+ body: req.body,
+ redirect: "manual",
+ // @ts-ignore
+ duplex: "half",
+ signal: controller.signal,
+ };
+
+ // #1815 try to refuse some request to some models
+ if (serverConfig.customModels && req.body) {
+ try {
+ const clonedBody = await req.text();
+ fetchOptions.body = clonedBody;
+
+ const jsonBody = JSON.parse(clonedBody) as { model?: string };
+
+ // not undefined and is false
+ if (
+ isModelAvailableInServer(
+ serverConfig.customModels,
+ jsonBody?.model as string,
+ ServiceProvider.ChatGLM as string,
+ )
+ ) {
+ return NextResponse.json(
+ {
+ error: true,
+ message: `you are not allowed to use ${jsonBody?.model} model`,
+ },
+ {
+ status: 403,
+ },
+ );
+ }
+ } catch (e) {
+ console.error(`[GLM] filter`, e);
+ }
+ }
+ try {
+ const res = await fetch(fetchUrl, fetchOptions);
+
+ // to prevent browser prompt for credentials
+ const newHeaders = new Headers(res.headers);
+ newHeaders.delete("www-authenticate");
+ // to disable nginx buffering
+ newHeaders.set("X-Accel-Buffering", "no");
+
+ return new Response(res.body, {
+ status: res.status,
+ statusText: res.statusText,
+ headers: newHeaders,
+ });
+ } finally {
+ clearTimeout(timeoutId);
+ }
+}
diff --git a/app/client/api.ts b/app/client/api.ts
index 4238c2a264b..1da81e96448 100644
--- a/app/client/api.ts
+++ b/app/client/api.ts
@@ -21,6 +21,7 @@ import { HunyuanApi } from "./platforms/tencent";
import { MoonshotApi } from "./platforms/moonshot";
import { SparkApi } from "./platforms/iflytek";
import { XAIApi } from "./platforms/xai";
+import { ChatGLMApi } from "./platforms/glm";
export const ROLES = ["system", "user", "assistant"] as const;
export type MessageRole = (typeof ROLES)[number];
@@ -69,7 +70,7 @@ export interface ChatOptions {
config: LLMConfig;
onUpdate?: (message: string, chunk: string) => void;
- onFinish: (message: string) => void;
+ onFinish: (message: string, responseRes: Response) => void;
onError?: (err: Error) => void;
onController?: (controller: AbortController) => void;
onBeforeTool?: (tool: ChatMessageTool) => void;
@@ -156,6 +157,9 @@ export class ClientApi {
case ModelProvider.XAI:
this.llm = new XAIApi();
break;
+ case ModelProvider.ChatGLM:
+ this.llm = new ChatGLMApi();
+ break;
default:
this.llm = new ChatGPTApi();
}
@@ -244,6 +248,7 @@ export function getHeaders(ignoreHeaders: boolean = false) {
const isMoonshot = modelConfig.providerName === ServiceProvider.Moonshot;
const isIflytek = modelConfig.providerName === ServiceProvider.Iflytek;
const isXAI = modelConfig.providerName === ServiceProvider.XAI;
+ const isChatGLM = modelConfig.providerName === ServiceProvider.ChatGLM;
const isEnabledAccessControl = accessStore.enabledAccessControl();
const apiKey = isGoogle
? accessStore.googleApiKey
@@ -259,6 +264,8 @@ export function getHeaders(ignoreHeaders: boolean = false) {
? accessStore.moonshotApiKey
: isXAI
? accessStore.xaiApiKey
+ : isChatGLM
+ ? accessStore.chatglmApiKey
: isIflytek
? accessStore.iflytekApiKey && accessStore.iflytekApiSecret
? accessStore.iflytekApiKey + ":" + accessStore.iflytekApiSecret
@@ -274,6 +281,7 @@ export function getHeaders(ignoreHeaders: boolean = false) {
isMoonshot,
isIflytek,
isXAI,
+ isChatGLM,
apiKey,
isEnabledAccessControl,
};
@@ -338,6 +346,8 @@ export function getClientApi(provider: ServiceProvider): ClientApi {
return new ClientApi(ModelProvider.Iflytek);
case ServiceProvider.XAI:
return new ClientApi(ModelProvider.XAI);
+ case ServiceProvider.ChatGLM:
+ return new ClientApi(ModelProvider.ChatGLM);
default:
return new ClientApi(ModelProvider.GPT);
}
diff --git a/app/client/platforms/alibaba.ts b/app/client/platforms/alibaba.ts
index 86229a14705..6fe69e87ae2 100644
--- a/app/client/platforms/alibaba.ts
+++ b/app/client/platforms/alibaba.ts
@@ -143,6 +143,7 @@ export class QwenApi implements LLMApi {
let responseText = "";
let remainText = "";
let finished = false;
+ let responseRes: Response;
// animate response to make it looks smooth
function animateResponseText() {
@@ -172,7 +173,7 @@ export class QwenApi implements LLMApi {
const finish = () => {
if (!finished) {
finished = true;
- options.onFinish(responseText + remainText);
+ options.onFinish(responseText + remainText, responseRes);
}
};
@@ -188,6 +189,7 @@ export class QwenApi implements LLMApi {
"[Alibaba] request response content type: ",
contentType,
);
+ responseRes = res;
if (contentType?.startsWith("text/plain")) {
responseText = await res.clone().text();
@@ -254,7 +256,7 @@ export class QwenApi implements LLMApi {
const resJson = await res.json();
const message = this.extractMessage(resJson);
- options.onFinish(message);
+ options.onFinish(message, res);
}
} catch (e) {
console.log("[Request] failed to make a chat request", e);
diff --git a/app/client/platforms/anthropic.ts b/app/client/platforms/anthropic.ts
index 3645cbe6eac..6747221a861 100644
--- a/app/client/platforms/anthropic.ts
+++ b/app/client/platforms/anthropic.ts
@@ -317,13 +317,14 @@ export class ClaudeApi implements LLMApi {
};
try {
- controller.signal.onabort = () => options.onFinish("");
+ controller.signal.onabort = () =>
+ options.onFinish("", new Response(null, { status: 400 }));
const res = await fetch(path, payload);
const resJson = await res.json();
const message = this.extractMessage(resJson);
- options.onFinish(message);
+ options.onFinish(message, res);
} catch (e) {
console.error("failed to chat", e);
options.onError?.(e as Error);
diff --git a/app/client/platforms/baidu.ts b/app/client/platforms/baidu.ts
index 2511a696b9b..9e8c2f139b6 100644
--- a/app/client/platforms/baidu.ts
+++ b/app/client/platforms/baidu.ts
@@ -162,6 +162,7 @@ export class ErnieApi implements LLMApi {
let responseText = "";
let remainText = "";
let finished = false;
+ let responseRes: Response;
// animate response to make it looks smooth
function animateResponseText() {
@@ -191,7 +192,7 @@ export class ErnieApi implements LLMApi {
const finish = () => {
if (!finished) {
finished = true;
- options.onFinish(responseText + remainText);
+ options.onFinish(responseText + remainText, responseRes);
}
};
@@ -204,7 +205,7 @@ export class ErnieApi implements LLMApi {
clearTimeout(requestTimeoutId);
const contentType = res.headers.get("content-type");
console.log("[Baidu] request response content type: ", contentType);
-
+ responseRes = res;
if (contentType?.startsWith("text/plain")) {
responseText = await res.clone().text();
return finish();
@@ -267,7 +268,7 @@ export class ErnieApi implements LLMApi {
const resJson = await res.json();
const message = resJson?.result;
- options.onFinish(message);
+ options.onFinish(message, res);
}
} catch (e) {
console.log("[Request] failed to make a chat request", e);
diff --git a/app/client/platforms/bytedance.ts b/app/client/platforms/bytedance.ts
index 000a9e278db..a2f0660d828 100644
--- a/app/client/platforms/bytedance.ts
+++ b/app/client/platforms/bytedance.ts
@@ -130,6 +130,7 @@ export class DoubaoApi implements LLMApi {
let responseText = "";
let remainText = "";
let finished = false;
+ let responseRes: Response;
// animate response to make it looks smooth
function animateResponseText() {
@@ -159,7 +160,7 @@ export class DoubaoApi implements LLMApi {
const finish = () => {
if (!finished) {
finished = true;
- options.onFinish(responseText + remainText);
+ options.onFinish(responseText + remainText, responseRes);
}
};
@@ -175,7 +176,7 @@ export class DoubaoApi implements LLMApi {
"[ByteDance] request response content type: ",
contentType,
);
-
+ responseRes = res;
if (contentType?.startsWith("text/plain")) {
responseText = await res.clone().text();
return finish();
@@ -241,7 +242,7 @@ export class DoubaoApi implements LLMApi {
const resJson = await res.json();
const message = this.extractMessage(resJson);
- options.onFinish(message);
+ options.onFinish(message, res);
}
} catch (e) {
console.log("[Request] failed to make a chat request", e);
diff --git a/app/client/platforms/glm.ts b/app/client/platforms/glm.ts
new file mode 100644
index 00000000000..a7965947fab
--- /dev/null
+++ b/app/client/platforms/glm.ts
@@ -0,0 +1,197 @@
+"use client";
+import {
+ ApiPath,
+ CHATGLM_BASE_URL,
+ ChatGLM,
+ REQUEST_TIMEOUT_MS,
+} from "@/app/constant";
+import {
+ useAccessStore,
+ useAppConfig,
+ useChatStore,
+ ChatMessageTool,
+ usePluginStore,
+} from "@/app/store";
+import { stream } from "@/app/utils/chat";
+import {
+ ChatOptions,
+ getHeaders,
+ LLMApi,
+ LLMModel,
+ SpeechOptions,
+} from "../api";
+import { getClientConfig } from "@/app/config/client";
+import { getMessageTextContent } from "@/app/utils";
+import { RequestPayload } from "./openai";
+import { fetch } from "@/app/utils/stream";
+
+export class ChatGLMApi implements LLMApi {
+ private disableListModels = true;
+
+ path(path: string): string {
+ const accessStore = useAccessStore.getState();
+
+ let baseUrl = "";
+
+ if (accessStore.useCustomConfig) {
+ baseUrl = accessStore.chatglmUrl;
+ }
+
+ if (baseUrl.length === 0) {
+ const isApp = !!getClientConfig()?.isApp;
+ const apiPath = ApiPath.ChatGLM;
+ baseUrl = isApp ? CHATGLM_BASE_URL : apiPath;
+ }
+
+ if (baseUrl.endsWith("/")) {
+ baseUrl = baseUrl.slice(0, baseUrl.length - 1);
+ }
+ if (!baseUrl.startsWith("http") && !baseUrl.startsWith(ApiPath.ChatGLM)) {
+ baseUrl = "https://" + baseUrl;
+ }
+
+ console.log("[Proxy Endpoint] ", baseUrl, path);
+
+ return [baseUrl, path].join("/");
+ }
+
+ extractMessage(res: any) {
+ return res.choices?.at(0)?.message?.content ?? "";
+ }
+
+ speech(options: SpeechOptions): Promise {
+ throw new Error("Method not implemented.");
+ }
+
+ async chat(options: ChatOptions) {
+ const messages: ChatOptions["messages"] = [];
+ for (const v of options.messages) {
+ const content = getMessageTextContent(v);
+ messages.push({ role: v.role, content });
+ }
+
+ const modelConfig = {
+ ...useAppConfig.getState().modelConfig,
+ ...useChatStore.getState().currentSession().mask.modelConfig,
+ ...{
+ model: options.config.model,
+ providerName: options.config.providerName,
+ },
+ };
+
+ const requestPayload: RequestPayload = {
+ messages,
+ stream: options.config.stream,
+ model: modelConfig.model,
+ temperature: modelConfig.temperature,
+ presence_penalty: modelConfig.presence_penalty,
+ frequency_penalty: modelConfig.frequency_penalty,
+ top_p: modelConfig.top_p,
+ };
+
+ console.log("[Request] glm payload: ", requestPayload);
+
+ const shouldStream = !!options.config.stream;
+ const controller = new AbortController();
+ options.onController?.(controller);
+
+ try {
+ const chatPath = this.path(ChatGLM.ChatPath);
+ const chatPayload = {
+ method: "POST",
+ body: JSON.stringify(requestPayload),
+ signal: controller.signal,
+ headers: getHeaders(),
+ };
+
+ // make a fetch request
+ const requestTimeoutId = setTimeout(
+ () => controller.abort(),
+ REQUEST_TIMEOUT_MS,
+ );
+
+ if (shouldStream) {
+ const [tools, funcs] = usePluginStore
+ .getState()
+ .getAsTools(
+ useChatStore.getState().currentSession().mask?.plugin || [],
+ );
+ return stream(
+ chatPath,
+ requestPayload,
+ getHeaders(),
+ tools as any,
+ funcs,
+ controller,
+ // parseSSE
+ (text: string, runTools: ChatMessageTool[]) => {
+ // console.log("parseSSE", text, runTools);
+ const json = JSON.parse(text);
+ const choices = json.choices as Array<{
+ delta: {
+ content: string;
+ tool_calls: ChatMessageTool[];
+ };
+ }>;
+ const tool_calls = choices[0]?.delta?.tool_calls;
+ if (tool_calls?.length > 0) {
+ const index = tool_calls[0]?.index;
+ const id = tool_calls[0]?.id;
+ const args = tool_calls[0]?.function?.arguments;
+ if (id) {
+ runTools.push({
+ id,
+ type: tool_calls[0]?.type,
+ function: {
+ name: tool_calls[0]?.function?.name as string,
+ arguments: args,
+ },
+ });
+ } else {
+ // @ts-ignore
+ runTools[index]["function"]["arguments"] += args;
+ }
+ }
+ return choices[0]?.delta?.content;
+ },
+ // processToolMessage, include tool_calls message and tool call results
+ (
+ requestPayload: RequestPayload,
+ toolCallMessage: any,
+ toolCallResult: any[],
+ ) => {
+ // @ts-ignore
+ requestPayload?.messages?.splice(
+ // @ts-ignore
+ requestPayload?.messages?.length,
+ 0,
+ toolCallMessage,
+ ...toolCallResult,
+ );
+ },
+ options,
+ );
+ } else {
+ const res = await fetch(chatPath, chatPayload);
+ clearTimeout(requestTimeoutId);
+
+ const resJson = await res.json();
+ const message = this.extractMessage(resJson);
+ options.onFinish(message, res);
+ }
+ } catch (e) {
+ console.log("[Request] failed to make a chat request", e);
+ options.onError?.(e as Error);
+ }
+ }
+ async usage() {
+ return {
+ used: 0,
+ total: 0,
+ };
+ }
+
+ async models(): Promise {
+ return [];
+ }
+}
diff --git a/app/client/platforms/google.ts b/app/client/platforms/google.ts
index 7265a500b97..53ff00aeed0 100644
--- a/app/client/platforms/google.ts
+++ b/app/client/platforms/google.ts
@@ -192,7 +192,10 @@ export class GeminiProApi implements LLMApi {
requestPayload,
getHeaders(),
// @ts-ignore
- [{ functionDeclarations: tools.map((tool) => tool.function) }],
+ tools.length > 0
+ ? // @ts-ignore
+ [{ functionDeclarations: tools.map((tool) => tool.function) }]
+ : [],
funcs,
controller,
// parseSSE
@@ -271,7 +274,7 @@ export class GeminiProApi implements LLMApi {
);
}
const message = apiClient.extractMessage(resJson);
- options.onFinish(message);
+ options.onFinish(message, res);
}
} catch (e) {
console.log("[Request] failed to make a chat request", e);
diff --git a/app/client/platforms/iflytek.ts b/app/client/platforms/iflytek.ts
index 55a39d0ccca..cfc37b3b256 100644
--- a/app/client/platforms/iflytek.ts
+++ b/app/client/platforms/iflytek.ts
@@ -117,6 +117,7 @@ export class SparkApi implements LLMApi {
let responseText = "";
let remainText = "";
let finished = false;
+ let responseRes: Response;
// Animate response text to make it look smooth
function animateResponseText() {
@@ -143,7 +144,7 @@ export class SparkApi implements LLMApi {
const finish = () => {
if (!finished) {
finished = true;
- options.onFinish(responseText + remainText);
+ options.onFinish(responseText + remainText, responseRes);
}
};
@@ -156,7 +157,7 @@ export class SparkApi implements LLMApi {
clearTimeout(requestTimeoutId);
const contentType = res.headers.get("content-type");
console.log("[Spark] request response content type: ", contentType);
-
+ responseRes = res;
if (contentType?.startsWith("text/plain")) {
responseText = await res.clone().text();
return finish();
@@ -231,7 +232,7 @@ export class SparkApi implements LLMApi {
const resJson = await res.json();
const message = this.extractMessage(resJson);
- options.onFinish(message);
+ options.onFinish(message, res);
}
} catch (e) {
console.log("[Request] failed to make a chat request", e);
diff --git a/app/client/platforms/moonshot.ts b/app/client/platforms/moonshot.ts
index 22a34b2e22f..b6812c0d766 100644
--- a/app/client/platforms/moonshot.ts
+++ b/app/client/platforms/moonshot.ts
@@ -180,7 +180,7 @@ export class MoonshotApi implements LLMApi {
const resJson = await res.json();
const message = this.extractMessage(resJson);
- options.onFinish(message);
+ options.onFinish(message, res);
}
} catch (e) {
console.log("[Request] failed to make a chat request", e);
diff --git a/app/client/platforms/openai.ts b/app/client/platforms/openai.ts
index 30f7415c141..6e893ed148f 100644
--- a/app/client/platforms/openai.ts
+++ b/app/client/platforms/openai.ts
@@ -361,7 +361,7 @@ export class ChatGPTApi implements LLMApi {
const resJson = await res.json();
const message = await this.extractMessage(resJson);
- options.onFinish(message);
+ options.onFinish(message, res);
}
} catch (e) {
console.log("[Request] failed to make a chat request", e);
diff --git a/app/client/platforms/tencent.ts b/app/client/platforms/tencent.ts
index 3610fac0a48..580844a5b31 100644
--- a/app/client/platforms/tencent.ts
+++ b/app/client/platforms/tencent.ts
@@ -142,6 +142,7 @@ export class HunyuanApi implements LLMApi {
let responseText = "";
let remainText = "";
let finished = false;
+ let responseRes: Response;
// animate response to make it looks smooth
function animateResponseText() {
@@ -171,7 +172,7 @@ export class HunyuanApi implements LLMApi {
const finish = () => {
if (!finished) {
finished = true;
- options.onFinish(responseText + remainText);
+ options.onFinish(responseText + remainText, responseRes);
}
};
@@ -187,7 +188,7 @@ export class HunyuanApi implements LLMApi {
"[Tencent] request response content type: ",
contentType,
);
-
+ responseRes = res;
if (contentType?.startsWith("text/plain")) {
responseText = await res.clone().text();
return finish();
@@ -253,7 +254,7 @@ export class HunyuanApi implements LLMApi {
const resJson = await res.json();
const message = this.extractMessage(resJson);
- options.onFinish(message);
+ options.onFinish(message, res);
}
} catch (e) {
console.log("[Request] failed to make a chat request", e);
diff --git a/app/client/platforms/xai.ts b/app/client/platforms/xai.ts
index deb74e66c03..06dbaaa29ff 100644
--- a/app/client/platforms/xai.ts
+++ b/app/client/platforms/xai.ts
@@ -173,7 +173,7 @@ export class XAIApi implements LLMApi {
const resJson = await res.json();
const message = this.extractMessage(resJson);
- options.onFinish(message);
+ options.onFinish(message, res);
}
} catch (e) {
console.log("[Request] failed to make a chat request", e);
diff --git a/app/components/settings.tsx b/app/components/settings.tsx
index 666caece838..e2666b5512c 100644
--- a/app/components/settings.tsx
+++ b/app/components/settings.tsx
@@ -72,6 +72,7 @@ import {
Stability,
Iflytek,
SAAS_CHAT_URL,
+ ChatGLM,
} from "../constant";
import { Prompt, SearchService, usePromptStore } from "../store/prompt";
import { ErrorBoundary } from "./error";
@@ -1234,6 +1235,47 @@ export function Settings() {
>
);
+ const chatglmConfigComponent = accessStore.provider ===
+ ServiceProvider.ChatGLM && (
+ <>
+
+
+ accessStore.update(
+ (access) => (access.chatglmUrl = e.currentTarget.value),
+ )
+ }
+ >
+
+
+ {
+ accessStore.update(
+ (access) => (access.chatglmApiKey = e.currentTarget.value),
+ );
+ }}
+ />
+
+ >
+ );
+
const stabilityConfigComponent = accessStore.provider ===
ServiceProvider.Stability && (
<>
@@ -1693,6 +1735,7 @@ export function Settings() {
{stabilityConfigComponent}
{lflytekConfigComponent}
{XAIConfigComponent}
+ {chatglmConfigComponent}
>
)}
>
diff --git a/app/config/server.ts b/app/config/server.ts
index eac4ba0cf33..485f950da03 100644
--- a/app/config/server.ts
+++ b/app/config/server.ts
@@ -75,6 +75,10 @@ declare global {
XAI_URL?: string;
XAI_API_KEY?: string;
+ // chatglm only
+ CHATGLM_URL?: string;
+ CHATGLM_API_KEY?: string;
+
// custom template for preprocessing user input
DEFAULT_INPUT_TEMPLATE?: string;
}
@@ -151,6 +155,7 @@ export const getServerSideConfig = () => {
const isMoonshot = !!process.env.MOONSHOT_API_KEY;
const isIflytek = !!process.env.IFLYTEK_API_KEY;
const isXAI = !!process.env.XAI_API_KEY;
+ const isChatGLM = !!process.env.CHATGLM_API_KEY;
// const apiKeyEnvVar = process.env.OPENAI_API_KEY ?? "";
// const apiKeys = apiKeyEnvVar.split(",").map((v) => v.trim());
// const randomIndex = Math.floor(Math.random() * apiKeys.length);
@@ -217,6 +222,10 @@ export const getServerSideConfig = () => {
xaiUrl: process.env.XAI_URL,
xaiApiKey: getApiKey(process.env.XAI_API_KEY),
+ isChatGLM,
+ chatglmUrl: process.env.CHATGLM_URL,
+ chatglmApiKey: getApiKey(process.env.CHATGLM_API_KEY),
+
cloudflareAccountId: process.env.CLOUDFLARE_ACCOUNT_ID,
cloudflareKVNamespaceId: process.env.CLOUDFLARE_KV_NAMESPACE_ID,
cloudflareKVApiKey: getApiKey(process.env.CLOUDFLARE_KV_API_KEY),
diff --git a/app/constant.ts b/app/constant.ts
index 00b8d9dae95..435969733e1 100644
--- a/app/constant.ts
+++ b/app/constant.ts
@@ -30,6 +30,8 @@ export const IFLYTEK_BASE_URL = "https://spark-api-open.xf-yun.com";
export const XAI_BASE_URL = "https://api.x.ai";
+export const CHATGLM_BASE_URL = "https://open.bigmodel.cn";
+
export const CACHE_URL_PREFIX = "/api/cache";
export const UPLOAD_URL = `${CACHE_URL_PREFIX}/upload`;
@@ -62,6 +64,7 @@ export enum ApiPath {
Stability = "/api/stability",
Artifacts = "/api/artifacts",
XAI = "/api/xai",
+ ChatGLM = "/api/chatglm",
}
export enum SlotID {
@@ -115,6 +118,7 @@ export enum ServiceProvider {
Stability = "Stability",
Iflytek = "Iflytek",
XAI = "XAI",
+ ChatGLM = "ChatGLM",
}
// Google API safety settings, see https://ai.google.dev/gemini-api/docs/safety-settings
@@ -138,6 +142,7 @@ export enum ModelProvider {
Moonshot = "Moonshot",
Iflytek = "Iflytek",
XAI = "XAI",
+ ChatGLM = "ChatGLM",
}
export const Stability = {
@@ -225,6 +230,11 @@ export const XAI = {
ChatPath: "v1/chat/completions",
};
+export const ChatGLM = {
+ ExampleEndpoint: CHATGLM_BASE_URL,
+ ChatPath: "/api/paas/v4/chat/completions",
+};
+
export const DEFAULT_INPUT_TEMPLATE = `{{input}}`; // input / time / model / lang
// export const DEFAULT_SYSTEM_TEMPLATE = `
// You are ChatGPT, a large language model trained by {{ServiceProvider}}.
@@ -317,8 +327,11 @@ const anthropicModels = [
"claude-2.1",
"claude-3-sonnet-20240229",
"claude-3-opus-20240229",
+ "claude-3-opus-latest",
"claude-3-haiku-20240307",
"claude-3-5-sonnet-20240620",
+ "claude-3-5-sonnet-20241022",
+ "claude-3-5-sonnet-latest",
"claude-3-5-haiku-latest",
];
@@ -377,6 +390,17 @@ const iflytekModels = [
const xAIModes = ["grok-beta"];
+const chatglmModels = [
+ "glm-4-plus",
+ "glm-4-0520",
+ "glm-4",
+ "glm-4-air",
+ "glm-4-airx",
+ "glm-4-long",
+ "glm-4-flashx",
+ "glm-4-flash",
+];
+
let seq = 1000; // 内置的模型序号生成器从1000开始
export const DEFAULT_MODELS = [
...openaiModels.map((name) => ({
@@ -500,6 +524,17 @@ export const DEFAULT_MODELS = [
sorted: 11,
},
})),
+ ...chatglmModels.map((name) => ({
+ name,
+ available: true,
+ sorted: seq++,
+ provider: {
+ id: "chatglm",
+ providerName: "ChatGLM",
+ providerType: "chatglm",
+ sorted: 12,
+ },
+ })),
] as const;
export const CHAT_PAGE_SIZE = 15;
diff --git a/app/locales/cn.ts b/app/locales/cn.ts
index 006fc81620d..9712593c61b 100644
--- a/app/locales/cn.ts
+++ b/app/locales/cn.ts
@@ -473,6 +473,17 @@ const cn = {
SubTitle: "样例:",
},
},
+ ChatGLM: {
+ ApiKey: {
+ Title: "接口密钥",
+ SubTitle: "使用自定义 ChatGLM API Key",
+ Placeholder: "ChatGLM API Key",
+ },
+ Endpoint: {
+ Title: "接口地址",
+ SubTitle: "样例:",
+ },
+ },
Stability: {
ApiKey: {
Title: "接口密钥",
diff --git a/app/locales/en.ts b/app/locales/en.ts
index 7204bd94696..ac8d3aed2a3 100644
--- a/app/locales/en.ts
+++ b/app/locales/en.ts
@@ -457,6 +457,17 @@ const en: LocaleType = {
SubTitle: "Example: ",
},
},
+ ChatGLM: {
+ ApiKey: {
+ Title: "ChatGLM API Key",
+ SubTitle: "Use a custom ChatGLM API Key",
+ Placeholder: "ChatGLM API Key",
+ },
+ Endpoint: {
+ Title: "Endpoint Address",
+ SubTitle: "Example: ",
+ },
+ },
Stability: {
ApiKey: {
Title: "Stability API Key",
diff --git a/app/store/access.ts b/app/store/access.ts
index b3d412a2daf..3b0e6357bc1 100644
--- a/app/store/access.ts
+++ b/app/store/access.ts
@@ -14,6 +14,7 @@ import {
STABILITY_BASE_URL,
IFLYTEK_BASE_URL,
XAI_BASE_URL,
+ CHATGLM_BASE_URL,
} from "../constant";
import { getHeaders } from "../client/api";
import { getClientConfig } from "../config/client";
@@ -47,6 +48,8 @@ const DEFAULT_IFLYTEK_URL = isApp ? IFLYTEK_BASE_URL : ApiPath.Iflytek;
const DEFAULT_XAI_URL = isApp ? XAI_BASE_URL : ApiPath.XAI;
+const DEFAULT_CHATGLM_URL = isApp ? CHATGLM_BASE_URL : ApiPath.ChatGLM;
+
const DEFAULT_ACCESS_STATE = {
accessCode: "",
useCustomConfig: false,
@@ -108,6 +111,10 @@ const DEFAULT_ACCESS_STATE = {
xaiUrl: DEFAULT_XAI_URL,
xaiApiKey: "",
+ // chatglm
+ chatglmUrl: DEFAULT_CHATGLM_URL,
+ chatglmApiKey: "",
+
// server config
needCode: true,
hideUserApiKey: false,
@@ -180,6 +187,10 @@ export const useAccessStore = createPersistStore(
return ensure(get(), ["xaiApiKey"]);
},
+ isValidChatGLM() {
+ return ensure(get(), ["chatglmApiKey"]);
+ },
+
isAuthorized() {
this.fetch();
@@ -196,6 +207,7 @@ export const useAccessStore = createPersistStore(
this.isValidMoonshot() ||
this.isValidIflytek() ||
this.isValidXAI() ||
+ this.isValidChatGLM() ||
!this.enabledAccessControl() ||
(this.enabledAccessControl() && ensure(get(), ["accessCode"]))
);
diff --git a/app/store/chat.ts b/app/store/chat.ts
index 6900899e144..1bf2e13677b 100644
--- a/app/store/chat.ts
+++ b/app/store/chat.ts
@@ -649,13 +649,14 @@ export const useChatStore = createPersistStore(
stream: false,
providerName,
},
- onFinish(message) {
- if (!isValidMessage(message)) return;
- get().updateCurrentSession(
- (session) =>
- (session.topic =
- message.length > 0 ? trimTopic(message) : DEFAULT_TOPIC),
- );
+ onFinish(message, responseRes) {
+ if (responseRes?.status === 200) {
+ get().updateCurrentSession(
+ (session) =>
+ (session.topic =
+ message.length > 0 ? trimTopic(message) : DEFAULT_TOPIC),
+ );
+ }
},
});
}
@@ -669,7 +670,7 @@ export const useChatStore = createPersistStore(
const historyMsgLength = countMessages(toBeSummarizedMsgs);
- if (historyMsgLength > modelConfig?.max_tokens ?? 4000) {
+ if (historyMsgLength > (modelConfig?.max_tokens || 4000)) {
const n = toBeSummarizedMsgs.length;
toBeSummarizedMsgs = toBeSummarizedMsgs.slice(
Math.max(0, n - modelConfig.historyMessageCount),
@@ -715,22 +716,20 @@ export const useChatStore = createPersistStore(
onUpdate(message) {
session.memoryPrompt = message;
},
- onFinish(message) {
- console.log("[Memory] ", message);
- get().updateCurrentSession((session) => {
- session.lastSummarizeIndex = lastSummarizeIndex;
- session.memoryPrompt = message; // Update the memory prompt for stored it in local storage
- });
+ onFinish(message, responseRes) {
+ if (responseRes?.status === 200) {
+ console.log("[Memory] ", message);
+ 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);
},
});
}
-
- function isValidMessage(message: any): boolean {
- return typeof message === "string" && !message.startsWith("```json");
- }
},
updateStat(message: ChatMessage) {
diff --git a/app/utils.ts b/app/utils.ts
index d8fc46330d1..2e1f9401607 100644
--- a/app/utils.ts
+++ b/app/utils.ts
@@ -266,7 +266,9 @@ export function isVisionModel(model: string) {
model.includes("gpt-4-turbo") && !model.includes("preview");
return (
- visionKeywords.some((keyword) => model.includes(keyword)) || isGpt4Turbo
+ visionKeywords.some((keyword) => model.includes(keyword)) ||
+ isGpt4Turbo ||
+ isDalle3(model)
);
}
@@ -278,7 +280,8 @@ export function showPlugins(provider: ServiceProvider, model: string) {
if (
provider == ServiceProvider.OpenAI ||
provider == ServiceProvider.Azure ||
- provider == ServiceProvider.Moonshot
+ provider == ServiceProvider.Moonshot ||
+ provider == ServiceProvider.ChatGLM
) {
return true;
}
diff --git a/app/utils/chat.ts b/app/utils/chat.ts
index ba19046252d..9209b5da540 100644
--- a/app/utils/chat.ts
+++ b/app/utils/chat.ts
@@ -174,6 +174,7 @@ export function stream(
let finished = false;
let running = false;
let runTools: any[] = [];
+ let responseRes: Response;
// animate response to make it looks smooth
function animateResponseText() {
@@ -272,7 +273,7 @@ export function stream(
}
console.debug("[ChatAPI] end");
finished = true;
- options.onFinish(responseText + remainText);
+ options.onFinish(responseText + remainText, responseRes); // 将res传递给onFinish
}
};
@@ -304,6 +305,7 @@ export function stream(
clearTimeout(requestTimeoutId);
const contentType = res.headers.get("content-type");
console.log("[Request] response content type: ", contentType);
+ responseRes = res;
if (contentType?.startsWith("text/plain")) {
responseText = await res.clone().text();
diff --git a/app/utils/stream.ts b/app/utils/stream.ts
index 2eda768f36f..f186730f6da 100644
--- a/app/utils/stream.ts
+++ b/app/utils/stream.ts
@@ -19,7 +19,7 @@ type StreamResponse = {
headers: Record;
};
-export function fetch(url: string, options?: RequestInit): Promise {
+export function fetch(url: string, options?: RequestInit): Promise {
if (window.__TAURI__) {
const {
signal,
@@ -100,7 +100,8 @@ export function fetch(url: string, options?: RequestInit): Promise {
})
.catch((e) => {
console.error("stream error", e);
- throw e;
+ // throw e;
+ return new Response("", { status: 599 });
});
}
return window.fetch(url, options);
diff --git a/docs/bt-cn.md b/docs/bt-cn.md
new file mode 100644
index 00000000000..115fbbd7007
--- /dev/null
+++ b/docs/bt-cn.md
@@ -0,0 +1,29 @@
+# 宝塔面板 的部署说明
+
+## 拥有自己的宝塔
+当你需要通过 宝塔面板 部署本项目之前,需要在服务器上先安装好 宝塔面板工具。 接下来的 部署流程 都建立在已有宝塔面板的前提下。宝塔安装请参考 ([宝塔官网](https://www.bt.cn/new/download.html))
+
+> 注意:本项目需要宝塔面板版本 9.2.0 及以上
+
+## 一键安装
+![bt-install-1](./images/bt/bt-install-1.jpeg)
+1. 在 宝塔面板 -> Docker -> 应用商店 页面,搜索 ChatGPT-Next-Web 找到本项目的docker应用;
+2. 点击 安装 开始部署本项目
+
+![bt-install-2](./images/bt/bt-install-2.jpeg)
+1. 在项目配置页,根据要求开始配置环境变量;
+2. 如勾选 允许外部访问 配置,请注意为配置的 web端口 开放安全组端口访问权限;
+3. 请确保你添加了正确的 Open Api Key,否则无法使用;当配置 OpenAI官方 提供的key(国内无法访问),请配置代理地址;
+4. 建议配置 访问权限密码,否则部署后所有人均可使用已配置的 Open Api Key(当允许外部访问时);
+5. 点击 确认 开始自动部署。
+
+## 如何访问
+![bt-install-3](./images/bt/bt-install-3.jpeg)
+通过根据 服务器IP地址 和配置的 web端口 http://$(host):$(port),在浏览器中打开 ChatGPT-Next-Web。
+
+![bt-install-4](./images/bt/bt-install-4.jpeg)
+若配置了 访问权限密码,访问大模型前需要登录,请点击 登录,获取访问权限。
+
+![bt-install-5](./images/bt/bt-install-5.jpeg)
+
+![bt-install-6](./images/bt/bt-install-6.jpeg)
diff --git a/docs/images/bt/bt-install-1.jpeg b/docs/images/bt/bt-install-1.jpeg
new file mode 100644
index 00000000000..fff3406d656
Binary files /dev/null and b/docs/images/bt/bt-install-1.jpeg differ
diff --git a/docs/images/bt/bt-install-2.jpeg b/docs/images/bt/bt-install-2.jpeg
new file mode 100644
index 00000000000..77256ef8d36
Binary files /dev/null and b/docs/images/bt/bt-install-2.jpeg differ
diff --git a/docs/images/bt/bt-install-3.jpeg b/docs/images/bt/bt-install-3.jpeg
new file mode 100644
index 00000000000..7790f89e850
Binary files /dev/null and b/docs/images/bt/bt-install-3.jpeg differ
diff --git a/docs/images/bt/bt-install-4.jpeg b/docs/images/bt/bt-install-4.jpeg
new file mode 100644
index 00000000000..38d7caee4cb
Binary files /dev/null and b/docs/images/bt/bt-install-4.jpeg differ
diff --git a/docs/images/bt/bt-install-5.jpeg b/docs/images/bt/bt-install-5.jpeg
new file mode 100644
index 00000000000..aa1a7963cb9
Binary files /dev/null and b/docs/images/bt/bt-install-5.jpeg differ
diff --git a/docs/images/bt/bt-install-6.jpeg b/docs/images/bt/bt-install-6.jpeg
new file mode 100644
index 00000000000..42359e65ba6
Binary files /dev/null and b/docs/images/bt/bt-install-6.jpeg differ
diff --git a/jest.setup.ts b/jest.setup.ts
index ee6ccea1a85..bc515f9a1b8 100644
--- a/jest.setup.ts
+++ b/jest.setup.ts
@@ -1,2 +1,24 @@
// Learn more: https://github.com/testing-library/jest-dom
import "@testing-library/jest-dom";
+
+global.fetch = jest.fn(() =>
+ Promise.resolve({
+ ok: true,
+ status: 200,
+ json: () => Promise.resolve({}),
+ headers: new Headers(),
+ redirected: false,
+ statusText: "OK",
+ type: "basic",
+ url: "",
+ clone: function () {
+ return this;
+ },
+ body: null,
+ bodyUsed: false,
+ arrayBuffer: () => Promise.resolve(new ArrayBuffer(0)),
+ blob: () => Promise.resolve(new Blob()),
+ formData: () => Promise.resolve(new FormData()),
+ text: () => Promise.resolve(""),
+ }),
+);
diff --git a/next.config.mjs b/next.config.mjs
index 26dadca4c9e..2bb6bc4f4b2 100644
--- a/next.config.mjs
+++ b/next.config.mjs
@@ -94,8 +94,12 @@ if (mode !== "export") {
source: "/sharegpt",
destination: "https://sharegpt.com/api/conversations",
},
+ {
+ source: "/api/proxy/alibaba/:path*",
+ destination: "https://dashscope.aliyuncs.com/api/:path*",
+ },
];
-
+
return {
beforeFiles: ret,
};
diff --git a/package.json b/package.json
index 803c0d1a4cb..a036969ac66 100644
--- a/package.json
+++ b/package.json
@@ -33,8 +33,8 @@
"html-to-image": "^1.11.11",
"idb-keyval": "^6.2.1",
"lodash-es": "^4.17.21",
- "mermaid": "^10.6.1",
"markdown-to-txt": "^2.0.1",
+ "mermaid": "^10.6.1",
"nanoid": "^5.0.3",
"next": "^14.1.1",
"node-fetch": "^3.3.1",
@@ -56,9 +56,10 @@
"devDependencies": {
"@tauri-apps/api": "^1.6.0",
"@tauri-apps/cli": "1.5.11",
- "@testing-library/jest-dom": "^6.4.8",
- "@testing-library/react": "^16.0.0",
- "@types/jest": "^29.5.13",
+ "@testing-library/dom": "^10.4.0",
+ "@testing-library/jest-dom": "^6.6.2",
+ "@testing-library/react": "^16.0.1",
+ "@types/jest": "^29.5.14",
"@types/js-yaml": "4.0.9",
"@types/lodash-es": "^4.17.12",
"@types/node": "^20.11.30",
diff --git a/src-tauri/src/stream.rs b/src-tauri/src/stream.rs
index d2c0726b0b4..8320db3e48a 100644
--- a/src-tauri/src/stream.rs
+++ b/src-tauri/src/stream.rs
@@ -119,11 +119,22 @@ pub async fn stream_fetch(
}
}
Err(err) => {
- println!("Error response: {:?}", err.source().expect("REASON").to_string());
+ let error: String = err.source()
+ .map(|e| e.to_string())
+ .unwrap_or_else(|| "Unknown error occurred".to_string());
+ println!("Error response: {:?}", error);
+ tauri::async_runtime::spawn( async move {
+ if let Err(e) = window.emit(event_name, ChunkPayload{ request_id, chunk: error.into() }) {
+ println!("Failed to emit chunk payload: {:?}", e);
+ }
+ if let Err(e) = window.emit(event_name, EndPayload{ request_id, status: 0 }) {
+ println!("Failed to emit end payload: {:?}", e);
+ }
+ });
StreamResponse {
request_id,
status: 599,
- status_text: err.source().expect("REASON").to_string(),
+ status_text: "Error".to_string(),
headers: HashMap::new(),
}
}
diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json
index b2c3e04b07b..415825b13f2 100644
--- a/src-tauri/tauri.conf.json
+++ b/src-tauri/tauri.conf.json
@@ -9,7 +9,7 @@
},
"package": {
"productName": "NextChat",
- "version": "2.15.5"
+ "version": "2.15.6"
},
"tauri": {
"allowlist": {
diff --git a/yarn.lock b/yarn.lock
index ef296924e09..16b8b872ee9 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -27,6 +27,15 @@
dependencies:
"@babel/highlight" "^7.18.6"
+"@babel/code-frame@^7.10.4":
+ version "7.26.0"
+ resolved "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.26.0.tgz#9374b5cd068d128dac0b94ff482594273b1c2815"
+ integrity sha512-INCKxTtbXtcNbUZ3YXutwMpEleqttcswhAdee7dhuoVrD2cnuc3PqtERBtxkX5nziX9vnBL8WXmSGwv8CuPV6g==
+ dependencies:
+ "@babel/helper-validator-identifier" "^7.25.9"
+ js-tokens "^4.0.0"
+ picocolors "^1.0.0"
+
"@babel/code-frame@^7.12.13", "@babel/code-frame@^7.24.7":
version "7.24.7"
resolved "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.24.7.tgz#882fd9e09e8ee324e496bd040401c6f046ef4465"
@@ -394,6 +403,11 @@
resolved "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz#75b889cfaf9e35c2aaf42cf0d72c8e91719251db"
integrity sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==
+"@babel/helper-validator-identifier@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz#24b64e2c3ec7cd3b3c547729b8d16871f22cbdc7"
+ integrity sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==
+
"@babel/helper-validator-option@^7.18.6", "@babel/helper-validator-option@^7.21.0":
version "7.21.0"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz#8224c7e13ace4bafdc4004da2cf064ef42673180"
@@ -1187,14 +1201,7 @@
resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310"
integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==
-"@babel/runtime@^7.12.1", "@babel/runtime@^7.20.7", "@babel/runtime@^7.23.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
- version "7.23.6"
- resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.6.tgz#c05e610dc228855dc92ef1b53d07389ed8ab521d"
- integrity sha512-zHd0eUrf5GZoOWVCXp6koAKQTfZV07eit6bGPmJgnZdnSAvvZee6zniW2XMF7Cmc4ISOOnPy3QaSiIJGJkVEDQ==
- dependencies:
- regenerator-runtime "^0.14.0"
-
-"@babel/runtime@^7.12.5", "@babel/runtime@^7.21.0":
+"@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.20.7", "@babel/runtime@^7.21.0", "@babel/runtime@^7.23.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
version "7.25.0"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.25.0.tgz#3af9a91c1b739c569d5d80cc917280919c544ecb"
integrity sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==
@@ -2093,13 +2100,26 @@
"@tauri-apps/cli-win32-ia32-msvc" "1.5.11"
"@tauri-apps/cli-win32-x64-msvc" "1.5.11"
-"@testing-library/jest-dom@^6.4.8":
- version "6.4.8"
- resolved "https://registry.npmmirror.com/@testing-library/jest-dom/-/jest-dom-6.4.8.tgz#9c435742b20c6183d4e7034f2b329d562c079daa"
- integrity sha512-JD0G+Zc38f5MBHA4NgxQMR5XtO5Jx9g86jqturNTt2WUfRmLDIY7iKkWHDCCTiDuFMre6nxAD5wHw9W5kI4rGw==
+"@testing-library/dom@^10.4.0":
+ version "10.4.0"
+ resolved "https://registry.npmmirror.com/@testing-library/dom/-/dom-10.4.0.tgz#82a9d9462f11d240ecadbf406607c6ceeeff43a8"
+ integrity sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==
+ dependencies:
+ "@babel/code-frame" "^7.10.4"
+ "@babel/runtime" "^7.12.5"
+ "@types/aria-query" "^5.0.1"
+ aria-query "5.3.0"
+ chalk "^4.1.0"
+ dom-accessibility-api "^0.5.9"
+ lz-string "^1.5.0"
+ pretty-format "^27.0.2"
+
+"@testing-library/jest-dom@^6.6.2":
+ version "6.6.2"
+ resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-6.6.2.tgz#8186aa9a07263adef9cc5a59a4772db8c31f4a5b"
+ integrity sha512-P6GJD4yqc9jZLbe98j/EkyQDTPgqftohZF5FBkHY5BUERZmcf4HeO2k0XaefEg329ux2p21i1A1DmyQ1kKw2Jw==
dependencies:
"@adobe/css-tools" "^4.4.0"
- "@babel/runtime" "^7.9.2"
aria-query "^5.0.0"
chalk "^3.0.0"
css.escape "^1.5.1"
@@ -2107,10 +2127,10 @@
lodash "^4.17.21"
redent "^3.0.0"
-"@testing-library/react@^16.0.0":
- version "16.0.0"
- resolved "https://registry.npmmirror.com/@testing-library/react/-/react-16.0.0.tgz#0a1e0c7a3de25841c3591b8cb7fb0cf0c0a27321"
- integrity sha512-guuxUKRWQ+FgNX0h0NS0FIq3Q3uLtWVpBzcLOggmfMoUpgBnzBzvLLd4fbm6yS8ydJd94cIfY4yP9qUQjM2KwQ==
+"@testing-library/react@^16.0.1":
+ version "16.0.1"
+ resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-16.0.1.tgz#29c0ee878d672703f5e7579f239005e4e0faa875"
+ integrity sha512-dSmwJVtJXmku+iocRhWOUFbrERC76TX2Mnf0ATODz8brzAZrMBbzLwQixlBSanZxR6LddK3eiwpSFZgDET1URg==
dependencies:
"@babel/runtime" "^7.12.5"
@@ -2144,6 +2164,11 @@
resolved "https://registry.npmmirror.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9"
integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==
+"@types/aria-query@^5.0.1":
+ version "5.0.4"
+ resolved "https://registry.npmmirror.com/@types/aria-query/-/aria-query-5.0.4.tgz#1a31c3d378850d2778dabb6374d036dcba4ba708"
+ integrity sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==
+
"@types/babel__core@^7.1.14":
version "7.20.5"
resolved "https://registry.npmmirror.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017"
@@ -2263,10 +2288,10 @@
dependencies:
"@types/istanbul-lib-report" "*"
-"@types/jest@^29.5.13":
- version "29.5.13"
- resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.13.tgz#8bc571659f401e6a719a7bf0dbcb8b78c71a8adc"
- integrity sha512-wd+MVEZCHt23V0/L642O5APvspWply/rGY5BcW4SUETo2UzPU3Z26qr8jC2qxpimI2jjx9h7+2cj2FwIr01bXg==
+"@types/jest@^29.5.14":
+ version "29.5.14"
+ resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.14.tgz#2b910912fa1d6856cadcd0c1f95af7df1d6049e5"
+ integrity sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==
dependencies:
expect "^29.0.0"
pretty-format "^29.0.0"
@@ -2738,20 +2763,13 @@ argparse@^2.0.1:
resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
-aria-query@^5.0.0:
+aria-query@5.3.0, aria-query@^5.0.0, aria-query@^5.1.3:
version "5.3.0"
resolved "https://registry.npmmirror.com/aria-query/-/aria-query-5.3.0.tgz#650c569e41ad90b51b3d7df5e5eed1c7549c103e"
integrity sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==
dependencies:
dequal "^2.0.3"
-aria-query@^5.1.3:
- version "5.1.3"
- resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.1.3.tgz#19db27cd101152773631396f7a95a3b58c22c35e"
- integrity sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==
- dependencies:
- deep-equal "^2.0.5"
-
array-buffer-byte-length@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz#fabe8bc193fea865f317fe7807085ee0dee5aead"
@@ -3081,7 +3099,7 @@ chalk@^3.0.0:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
-chalk@^4.0.0, chalk@^4.1.2:
+chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
@@ -3877,6 +3895,11 @@ doctrine@^3.0.0:
dependencies:
esutils "^2.0.2"
+dom-accessibility-api@^0.5.9:
+ version "0.5.16"
+ resolved "https://registry.npmmirror.com/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz#5a7429e6066eb3664d911e33fb0e45de8eb08453"
+ integrity sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==
+
dom-accessibility-api@^0.6.3:
version "0.6.3"
resolved "https://registry.npmmirror.com/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz#993e925cc1d73f2c662e7d75dd5a5445259a8fd8"
@@ -6052,6 +6075,11 @@ lru-cache@^6.0.0:
dependencies:
yallist "^4.0.0"
+lz-string@^1.5.0:
+ version "1.5.0"
+ resolved "https://registry.npmmirror.com/lz-string/-/lz-string-1.5.0.tgz#c1ab50f77887b712621201ba9fd4e3a6ed099941"
+ integrity sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==
+
make-dir@^4.0.0:
version "4.0.0"
resolved "https://registry.npmmirror.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e"
@@ -7018,6 +7046,15 @@ prettier@^3.0.2:
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.0.2.tgz#78fcecd6d870551aa5547437cdae39d4701dca5b"
integrity sha512-o2YR9qtniXvwEZlOKbveKfDQVyqxbEIWn48Z8m3ZJjBjcCmUy3xZGIv+7AkaeuaTr6yPXJjwv07ZWlsWbEy1rQ==
+pretty-format@^27.0.2:
+ version "27.5.1"
+ resolved "https://registry.npmmirror.com/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e"
+ integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==
+ dependencies:
+ ansi-regex "^5.0.1"
+ ansi-styles "^5.0.0"
+ react-is "^17.0.1"
+
pretty-format@^29.0.0, pretty-format@^29.7.0:
version "29.7.0"
resolved "https://registry.npmmirror.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812"
@@ -7109,6 +7146,11 @@ react-is@^16.13.1, react-is@^16.7.0:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
+react-is@^17.0.1:
+ version "17.0.2"
+ resolved "https://registry.npmmirror.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
+ integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
+
react-is@^18.0.0:
version "18.2.0"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b"