Merge remote-tracking branch 'upstream/main'

This commit is contained in:
Hk-Gosuto 2023-12-25 12:39:24 +08:00
commit da29a94714
35 changed files with 894 additions and 816 deletions

8
.dockerignore Normal file
View File

@ -0,0 +1,8 @@
# local env files
.env*.local
# docker-compose env files
.env
*.key
*.key.pub

View File

@ -8,6 +8,16 @@ CODE=your-password
# You can start service behind a proxy # You can start service behind a proxy
PROXY_URL=http://localhost:7890 PROXY_URL=http://localhost:7890
# (optional)
# Default: Empty
# Googel Gemini Pro API key, set if you want to use Google Gemini Pro API.
GOOGLE_API_KEY=
# (optional)
# Default: https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent
# Googel Gemini Pro API url, set if you want to customize Google Gemini Pro API url.
GOOGLE_URL=
# Override openai api request base url. (optional) # Override openai api request base url. (optional)
# Default: https://api.openai.com # Default: https://api.openai.com
# Examples: http://your-openai-proxy.com # Examples: http://your-openai-proxy.com
@ -40,4 +50,4 @@ DISABLE_FAST_LINK=
# (optional) # (optional)
# Default: 1 # Default: 1
# If your project is not deployed on Vercel, set this value to 1. # If your project is not deployed on Vercel, set this value to 1.
NEXT_PUBLIC_ENABLE_NODEJS_PLUGIN=1 NEXT_PUBLIC_ENABLE_NODEJS_PLUGIN=1

View File

@ -16,6 +16,7 @@ FROM base AS builder
RUN apk update && apk add --no-cache git RUN apk update && apk add --no-cache git
ENV OPENAI_API_KEY="" ENV OPENAI_API_KEY=""
ENV GOOGLE_API_KEY=""
ENV CODE="" ENV CODE=""
ENV NEXT_PUBLIC_ENABLE_NODEJS_PLUGIN=1 ENV NEXT_PUBLIC_ENABLE_NODEJS_PLUGIN=1
@ -32,6 +33,7 @@ RUN apk add proxychains-ng
ENV PROXY_URL="" ENV PROXY_URL=""
ENV OPENAI_API_KEY="" ENV OPENAI_API_KEY=""
ENV GOOGLE_API_KEY=""
ENV CODE="" ENV CODE=""
COPY --from=builder /app/public ./public COPY --from=builder /app/public ./public
@ -42,22 +44,22 @@ COPY --from=builder /app/.next/server ./.next/server
EXPOSE 3000 EXPOSE 3000
CMD if [ -n "$PROXY_URL" ]; then \ CMD if [ -n "$PROXY_URL" ]; then \
export HOSTNAME="127.0.0.1"; \ export HOSTNAME="127.0.0.1"; \
protocol=$(echo $PROXY_URL | cut -d: -f1); \ protocol=$(echo $PROXY_URL | cut -d: -f1); \
host=$(echo $PROXY_URL | cut -d/ -f3 | cut -d: -f1); \ host=$(echo $PROXY_URL | cut -d/ -f3 | cut -d: -f1); \
port=$(echo $PROXY_URL | cut -d: -f3); \ port=$(echo $PROXY_URL | cut -d: -f3); \
conf=/etc/proxychains.conf; \ conf=/etc/proxychains.conf; \
echo "strict_chain" > $conf; \ echo "strict_chain" > $conf; \
echo "proxy_dns" >> $conf; \ echo "proxy_dns" >> $conf; \
echo "remote_dns_subnet 224" >> $conf; \ echo "remote_dns_subnet 224" >> $conf; \
echo "tcp_read_time_out 15000" >> $conf; \ echo "tcp_read_time_out 15000" >> $conf; \
echo "tcp_connect_time_out 8000" >> $conf; \ echo "tcp_connect_time_out 8000" >> $conf; \
echo "localnet 127.0.0.0/255.0.0.0" >> $conf; \ echo "localnet 127.0.0.0/255.0.0.0" >> $conf; \
echo "localnet ::1/128" >> $conf; \ echo "localnet ::1/128" >> $conf; \
echo "[ProxyList]" >> $conf; \ echo "[ProxyList]" >> $conf; \
echo "$protocol $host $port" >> $conf; \ echo "$protocol $host $port" >> $conf; \
cat /etc/proxychains.conf; \ cat /etc/proxychains.conf; \
proxychains -f $conf node server.js; \ proxychains -f $conf node server.js; \
else \ else \
node server.js; \ node server.js; \
fi fi

View File

@ -3,7 +3,7 @@
<h1 align="center">ChatGPT Next Web LangChain</h1> <h1 align="center">ChatGPT Next Web LangChain</h1>
一键免费部署你的跨平台私人 ChatGPT 应用(基于 LangChain 实现插件功能) 一键免费部署你的跨平台私人 ChatGPT 应用, 支持 GPT3, GPT4 & Gemini Pro 模型。(基于 LangChain 实现插件功能)
[![Web][Web-image]][web-url] [![Web][Web-image]][web-url]
[![Windows][Windows-image]][download-url] [![Windows][Windows-image]][download-url]
@ -60,13 +60,10 @@
- 使用本插件需要一定的专业知识Stable Diffusion 本身的相关问题不在本项目的解答范围内,如果您确定要使用本插件请参考 [Stable Diffusion 插件配置指南](./docs/stable-diffusion-plugin-cn.md) 文档进行配置 - 使用本插件需要一定的专业知识Stable Diffusion 本身的相关问题不在本项目的解答范围内,如果您确定要使用本插件请参考 [Stable Diffusion 插件配置指南](./docs/stable-diffusion-plugin-cn.md) 文档进行配置
- StableDiffusion 插件需要配置对象存储服务,请参考 [对象存储服务配置指南](./docs/s3-oss.md) 配置 - StableDiffusion 插件需要配置对象存储服务,请参考 [对象存储服务配置指南](./docs/s3-oss.md) 配置
- Arxiv - Arxiv
- ⚠ 实验性支持 Gemini-Pro 模型 - 支持 Gemini-Pro 模型(同步上游仓库并修改接口为流式传输)
- 以下功能目前还不支持 - 以下功能目前还不支持
- `temperature` 等参数配置
- **面具**和**系统提示词**功能google 的相关接口不支持,后续支持会跟进)
- **插件功能** - **插件功能**
- 如何启用 - 如何启用
- 配置 `CUSTOM_MODELS` 添加 `gemini-pro` 模型,如:`CUSTOM_MODELS=gemini-pro`
- 配置密钥 `GOOGLE_API_KEY` key 可以在这里获取https://ai.google.dev/tutorials/setup - 配置密钥 `GOOGLE_API_KEY` key 可以在这里获取https://ai.google.dev/tutorials/setup
- 配置自定义接口地址(可选) `GOOGLE_BASE_URL`,可以使用我的这个项目搭建一个基于 vercel 的代理服务:[google-gemini-vercel-proxy](https://github.com/Hk-Gosuto/google-gemini-vercel-proxy) - 配置自定义接口地址(可选) `GOOGLE_BASE_URL`,可以使用我的这个项目搭建一个基于 vercel 的代理服务:[google-gemini-vercel-proxy](https://github.com/Hk-Gosuto/google-gemini-vercel-proxy)
- 常见问题参考:[Gemini Prompting FAQs](https://js.langchain.com/docs/integrations/chat/google_generativeai#gemini-prompting-faqs) - 常见问题参考:[Gemini Prompting FAQs](https://js.langchain.com/docs/integrations/chat/google_generativeai#gemini-prompting-faqs)
@ -194,6 +191,14 @@ OpenAI 接口代理 URL如果你手动配置了 openai 接口代理,请填
如果你不想让用户查询余额,将此环境变量设置为 1 即可。 如果你不想让用户查询余额,将此环境变量设置为 1 即可。
### `GOOGLE_API_KEY` (可选)
Google Gemini Pro Api Key.
### `GOOGLE_BASE_URL` (可选)
Google Gemini Pro Api Url.
## 部署 ## 部署
### 容器部署 (推荐) ### 容器部署 (推荐)

View File

@ -1,14 +1,16 @@
<div align="center"> <div align="center">
<img src="./docs/images/icon.svg" alt="预览"/> <img src="./docs/images/icon.svg" alt="预览"/>
<h1 align="center">ChatGPT Next Web</h1> <h1 align="center">NextChat</h1>
一键免费部署你的私人 ChatGPT 网页应用。 一键免费部署你的私人 ChatGPT 网页应用,支持 GPT3, GPT4 & Gemini Pro 模型
[演示 Demo](https://chat-gpt-next-web.vercel.app/) / [反馈 Issues](https://github.com/Yidadaa/ChatGPT-Next-Web/issues) / [加入 Discord](https://discord.gg/zrhvHCr79N) / [QQ 群](https://user-images.githubusercontent.com/16968934/228190818-7dd00845-e9b9-4363-97e5-44c507ac76da.jpeg) / [打赏开发者](https://user-images.githubusercontent.com/16968934/227772541-5bcd52d8-61b7-488c-a203-0330d8006e2b.jpg) / [Donate](#捐赠-donate-usdt) [演示 Demo](https://chat-gpt-next-web.vercel.app/) / [反馈 Issues](https://github.com/Yidadaa/ChatGPT-Next-Web/issues) / [加入 Discord](https://discord.gg/zrhvHCr79N) / [QQ 群](https://user-images.githubusercontent.com/16968934/228190818-7dd00845-e9b9-4363-97e5-44c507ac76da.jpeg) / [打赏开发者](https://user-images.githubusercontent.com/16968934/227772541-5bcd52d8-61b7-488c-a203-0330d8006e2b.jpg) / [Donate](#捐赠-donate-usdt)
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FYidadaa%2FChatGPT-Next-Web&env=OPENAI_API_KEY&env=CODE&project-name=chatgpt-next-web&repository-name=ChatGPT-Next-Web) [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FYidadaa%2FChatGPT-Next-Web&env=OPENAI_API_KEY&env=CODE&project-name=chatgpt-next-web&repository-name=ChatGPT-Next-Web)
[![Deploy on Zeabur](https://zeabur.com/button.svg)](https://zeabur.com/templates/ZBUEFA)
[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/Yidadaa/ChatGPT-Next-Web) [![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/Yidadaa/ChatGPT-Next-Web)
![主界面](./docs/images/cover.png) ![主界面](./docs/images/cover.png)
@ -19,7 +21,7 @@
1. 准备好你的 [OpenAI API Key](https://platform.openai.com/account/api-keys); 1. 准备好你的 [OpenAI API Key](https://platform.openai.com/account/api-keys);
2. 点击右侧按钮开始部署: 2. 点击右侧按钮开始部署:
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FYidadaa%2FChatGPT-Next-Web&env=OPENAI_API_KEY&env=CODE&project-name=chatgpt-next-web&repository-name=ChatGPT-Next-Web),直接使用 Github 账号登录即可,记得在环境变量页填入 API Key 和[页面访问密码](#配置页面访问密码) CODE [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FYidadaa%2FChatGPT-Next-Web&env=OPENAI_API_KEY&env=CODE&env=GOOGLE_API_KEY&project-name=chatgpt-next-web&repository-name=ChatGPT-Next-Web),直接使用 Github 账号登录即可,记得在环境变量页填入 API Key 和[页面访问密码](#配置页面访问密码) CODE
3. 部署完毕后,即可开始使用; 3. 部署完毕后,即可开始使用;
4. (可选)[绑定自定义域名](https://vercel.com/docs/concepts/projects/domains/add-a-domain)Vercel 分配的域名 DNS 在某些区域被污染了,绑定自定义域名即可直连。 4. (可选)[绑定自定义域名](https://vercel.com/docs/concepts/projects/domains/add-a-domain)Vercel 分配的域名 DNS 在某些区域被污染了,绑定自定义域名即可直连。
@ -104,6 +106,14 @@ Azure 密钥。
Azure Api 版本,你可以在这里找到:[Azure 文档](https://learn.microsoft.com/en-us/azure/ai-services/openai/reference#chat-completions)。 Azure Api 版本,你可以在这里找到:[Azure 文档](https://learn.microsoft.com/en-us/azure/ai-services/openai/reference#chat-completions)。
### `GOOGLE_API_KEY` (optional)
Google Gemini Pro 密钥.
### `GOOGLE_URL` (optional)
Google Gemini Pro Api Url.
### `HIDE_USER_API_KEY` (可选) ### `HIDE_USER_API_KEY` (可选)
如果你不想让用户自行填入 API Key将此环境变量设置为 1 即可。 如果你不想让用户自行填入 API Key将此环境变量设置为 1 即可。

View File

@ -1,7 +1,7 @@
import { NextRequest } from "next/server"; import { NextRequest } from "next/server";
import { getServerSideConfig } from "../config/server"; import { getServerSideConfig } from "../config/server";
import md5 from "spark-md5"; import md5 from "spark-md5";
import { ACCESS_CODE_PREFIX } from "../constant"; import { ACCESS_CODE_PREFIX, ModelProvider } from "../constant";
function getIP(req: NextRequest) { function getIP(req: NextRequest) {
let ip = req.ip ?? req.headers.get("x-real-ip"); let ip = req.ip ?? req.headers.get("x-real-ip");
@ -16,15 +16,15 @@ function getIP(req: NextRequest) {
function parseApiKey(bearToken: string) { function parseApiKey(bearToken: string) {
const token = bearToken.trim().replaceAll("Bearer ", "").trim(); const token = bearToken.trim().replaceAll("Bearer ", "").trim();
const isOpenAiKey = !token.startsWith(ACCESS_CODE_PREFIX); const isApiKey = !token.startsWith(ACCESS_CODE_PREFIX);
return { return {
accessCode: isOpenAiKey ? "" : token.slice(ACCESS_CODE_PREFIX.length), accessCode: isApiKey ? "" : token.slice(ACCESS_CODE_PREFIX.length),
apiKey: isOpenAiKey ? token : "", apiKey: isApiKey ? token : "",
}; };
} }
export function auth(req: NextRequest) { export function auth(req: NextRequest, modelProvider: ModelProvider) {
const authToken = req.headers.get("Authorization") ?? ""; const authToken = req.headers.get("Authorization") ?? "";
// check if it is openai api key or user token // check if it is openai api key or user token
@ -49,22 +49,23 @@ export function auth(req: NextRequest) {
if (serverConfig.hideUserApiKey && !!apiKey) { if (serverConfig.hideUserApiKey && !!apiKey) {
return { return {
error: true, error: true,
msg: "you are not allowed to access openai with your own api key", msg: "you are not allowed to access with your own api key",
}; };
} }
// if user does not provide an api key, inject system api key // if user does not provide an api key, inject system api key
if (!apiKey) { if (!apiKey) {
const serverApiKey = serverConfig.isAzure const serverConfig = getServerSideConfig();
? serverConfig.azureApiKey
: serverConfig.apiKey;
if (serverApiKey) { const systemApiKey =
modelProvider === ModelProvider.GeminiPro
? serverConfig.googleApiKey
: serverConfig.isAzure
? serverConfig.azureApiKey
: serverConfig.apiKey;
if (systemApiKey) {
console.log("[Auth] use system api key"); console.log("[Auth] use system api key");
req.headers.set( req.headers.set("Authorization", `Bearer ${systemApiKey}`);
"Authorization",
`${serverConfig.isAzure ? "" : "Bearer "}${serverApiKey}`,
);
} else { } else {
console.log("[Auth] admin did not provide an api key"); console.log("[Auth] admin did not provide an api key");
} }

View File

@ -1,6 +1,6 @@
import { NextRequest, NextResponse } from "next/server"; import { NextRequest, NextResponse } from "next/server";
import { getServerSideConfig } from "../config/server"; import { getServerSideConfig } from "../config/server";
import { DEFAULT_MODELS, GOOGLE_BASE_URL, OPENAI_BASE_URL } from "../constant"; import { DEFAULT_MODELS, OPENAI_BASE_URL, GEMINI_BASE_URL } from "../constant";
import { collectModelTable } from "../utils/model"; import { collectModelTable } from "../utils/model";
import { makeAzurePath } from "../azure"; import { makeAzurePath } from "../azure";
@ -9,7 +9,16 @@ const serverConfig = getServerSideConfig();
export async function requestOpenai(req: NextRequest) { export async function requestOpenai(req: NextRequest) {
const controller = new AbortController(); const controller = new AbortController();
const authValue = req.headers.get("Authorization") ?? ""; if (serverConfig.isAzure) {
const authValue =
req.headers
.get("Authorization")
?.trim()
.replaceAll("Bearer ", "")
.trim() ?? "";
} else {
const authValue = req.headers.get("Authorization") ?? "";
}
const authHeaderName = serverConfig.isAzure ? "api-key" : "Authorization"; const authHeaderName = serverConfig.isAzure ? "api-key" : "Authorization";
let path = `${req.nextUrl.pathname}${req.nextUrl.search}`.replaceAll( let path = `${req.nextUrl.pathname}${req.nextUrl.search}`.replaceAll(
@ -118,73 +127,3 @@ export async function requestOpenai(req: NextRequest) {
clearTimeout(timeoutId); clearTimeout(timeoutId);
} }
} }
export async function requestGoogleGemini(req: NextRequest) {
const controller = new AbortController();
const authValue =
req.headers.get("Authorization")?.replace("Bearer ", "") ?? "";
const authHeaderName = "x-goog-api-key";
console.log(req.nextUrl);
let path = `${req.nextUrl.pathname}`.replaceAll("/api/google/", "");
let baseUrl = serverConfig.googleBaseUrl || GOOGLE_BASE_URL;
if (!baseUrl.startsWith("http")) {
baseUrl = `https://${baseUrl}`;
}
if (baseUrl.endsWith("/")) {
baseUrl = baseUrl.slice(0, -1);
}
console.log("[Proxy] ", path);
console.log("[Google Base Url]", baseUrl);
// 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 fetchUrl = `${baseUrl}/${path}?alt=sse`;
const fetchOptions: RequestInit = {
headers: {
"Content-Type": "application/json",
"Cache-Control": "no-store",
[authHeaderName]: authValue,
},
method: req.method,
body: req.body,
// to fix #2485: https://stackoverflow.com/questions/55920957/cloudflare-worker-typeerror-one-time-use-body
redirect: "manual",
// @ts-ignore
duplex: "half",
signal: controller.signal,
};
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);
}
}

View File

@ -1,46 +1,98 @@
import { prettyObject } from "@/app/utils/format";
import { NextRequest, NextResponse } from "next/server"; import { NextRequest, NextResponse } from "next/server";
import { auth, googleAuth } from "../../auth"; import { auth } from "../../auth";
import { requestGoogleGemini } from "../../common"; import { getServerSideConfig } from "@/app/config/server";
import { GEMINI_BASE_URL, Google, ModelProvider } from "@/app/constant";
async function handle( async function handle(
req: NextRequest, req: NextRequest,
{ params }: { params: { path: string[] } }, { params }: { params: { path: string[] } },
) { ) {
console.log("[OpenAI Route] params ", params); console.log("[Google Route] params ", params);
if (req.method === "OPTIONS") { if (req.method === "OPTIONS") {
return NextResponse.json({ body: "OK" }, { status: 200 }); return NextResponse.json({ body: "OK" }, { status: 200 });
} }
const subpath = params.path.join("/"); const controller = new AbortController();
// if (!ALLOWD_PATH.has(subpath)) { const serverConfig = getServerSideConfig();
// console.log("[OpenAI Route] forbidden path ", subpath);
// return NextResponse.json(
// {
// error: true,
// msg: "you are not allowed to request " + subpath,
// },
// {
// status: 403,
// },
// );
// }
const authResult = googleAuth(req); let baseUrl = serverConfig.googleUrl || GEMINI_BASE_URL;
if (!baseUrl.startsWith("http")) {
baseUrl = `https://${baseUrl}`;
}
if (baseUrl.endsWith("/")) {
baseUrl = baseUrl.slice(0, -1);
}
let path = `${req.nextUrl.pathname}`.replaceAll("/api/google/", "");
console.log("[Proxy] ", path);
console.log("[Base Url]", baseUrl);
const timeoutId = setTimeout(
() => {
controller.abort();
},
10 * 60 * 1000,
);
const authResult = auth(req, ModelProvider.GeminiPro);
if (authResult.error) { if (authResult.error) {
return NextResponse.json(authResult, { return NextResponse.json(authResult, {
status: 401, status: 401,
}); });
} }
const bearToken = req.headers.get("Authorization") ?? "";
const token = bearToken.trim().replaceAll("Bearer ", "").trim();
const key = token ? token : serverConfig.googleApiKey;
if (!key) {
return NextResponse.json(
{
error: true,
message: `missing GOOGLE_API_KEY in server env vars`,
},
{
status: 401,
},
);
}
const fetchUrl = `${baseUrl}/${path}?key=${key}`;
const fetchOptions: RequestInit = {
headers: {
"Content-Type": "application/json",
"Cache-Control": "no-store",
},
method: req.method,
body: req.body,
// to fix #2485: https://stackoverflow.com/questions/55920957/cloudflare-worker-typeerror-one-time-use-body
redirect: "manual",
// @ts-ignore
duplex: "half",
signal: controller.signal,
};
try { try {
const response = await requestGoogleGemini(req); const res = await fetch(fetchUrl, fetchOptions);
return response; // to prevent browser prompt for credentials
} catch (e) { const newHeaders = new Headers(res.headers);
console.error("[OpenAI] ", e); newHeaders.delete("www-authenticate");
return NextResponse.json(prettyObject(e)); // 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);
} }
} }

View File

@ -1,6 +1,6 @@
import { type OpenAIListModelResponse } from "@/app/client/platforms/openai"; import { type OpenAIListModelResponse } from "@/app/client/platforms/openai";
import { getServerSideConfig } from "@/app/config/server"; import { getServerSideConfig } from "@/app/config/server";
import { OpenaiPath } from "@/app/constant"; import { ModelProvider, OpenaiPath } from "@/app/constant";
import { prettyObject } from "@/app/utils/format"; import { prettyObject } from "@/app/utils/format";
import { NextRequest, NextResponse } from "next/server"; import { NextRequest, NextResponse } from "next/server";
import { auth } from "../../auth"; import { auth } from "../../auth";
@ -45,7 +45,7 @@ async function handle(
); );
} }
const authResult = auth(req); const authResult = auth(req, ModelProvider.GPT);
if (authResult.error) { if (authResult.error) {
return NextResponse.json(authResult, { return NextResponse.json(authResult, {
status: 401, status: 401,
@ -75,4 +75,22 @@ export const GET = handle;
export const POST = handle; export const POST = handle;
export const runtime = "edge"; export const runtime = "edge";
export const preferredRegion = ['arn1', 'bom1', 'cdg1', 'cle1', 'cpt1', 'dub1', 'fra1', 'gru1', 'hnd1', 'iad1', 'icn1', 'kix1', 'lhr1', 'pdx1', 'sfo1', 'sin1', 'syd1']; export const preferredRegion = [
"arn1",
"bom1",
"cdg1",
"cle1",
"cpt1",
"dub1",
"fra1",
"gru1",
"hnd1",
"iad1",
"icn1",
"kix1",
"lhr1",
"pdx1",
"sfo1",
"sin1",
"syd1",
];

View File

@ -1,10 +1,14 @@
import { getClientConfig } from "../config/client"; import { getClientConfig } from "../config/client";
import { ACCESS_CODE_PREFIX, Azure, ServiceProvider } from "../constant"; import {
import { ChatMessage, ModelType, useAccessStore } from "../store"; ACCESS_CODE_PREFIX,
Azure,
ModelProvider,
ServiceProvider,
} from "../constant";
import { ChatMessage, ModelType, useAccessStore, useChatStore } from "../store";
import { ChatGPTApi } from "./platforms/openai"; import { ChatGPTApi } from "./platforms/openai";
import { GeminiApi } from "./platforms/google";
import { FileApi } from "./platforms/utils"; import { FileApi } from "./platforms/utils";
import { GeminiProApi } from "./platforms/google";
export const ROLES = ["system", "user", "assistant"] as const; export const ROLES = ["system", "user", "assistant"] as const;
export type MessageRole = (typeof ROLES)[number]; export type MessageRole = (typeof ROLES)[number];
@ -61,6 +65,13 @@ export interface LLMUsage {
export interface LLMModel { export interface LLMModel {
name: string; name: string;
available: boolean; available: boolean;
provider: LLMModelProvider;
}
export interface LLMModelProvider {
id: string;
providerName: string;
providerType: string;
} }
export abstract class LLMApi { export abstract class LLMApi {
@ -101,16 +112,15 @@ export class ClientApi {
public llm: LLMApi; public llm: LLMApi;
public file: FileApi; public file: FileApi;
constructor() { constructor(provider: ModelProvider = ModelProvider.GPT) {
if (provider === ModelProvider.GeminiPro) {
this.llm = new GeminiProApi();
return;
}
this.llm = new ChatGPTApi(); this.llm = new ChatGPTApi();
this.file = new FileApi(); this.file = new FileApi();
} }
switch(model: string) {
if (model.startsWith("gemini")) this.llm = new GeminiApi();
else this.llm = new ChatGPTApi();
}
config() {} config() {}
prompts() {} prompts() {}
@ -127,7 +137,7 @@ export class ClientApi {
{ {
from: "human", from: "human",
value: value:
"Share from [ChatGPT Next Web]: https://github.com/Yidadaa/ChatGPT-Next-Web", "Share from [NextChat]: https://github.com/Yidadaa/ChatGPT-Next-Web",
}, },
]); ]);
// 敬告二开开发者们,为了开源大模型的发展,请不要修改上述消息,此消息用于后续数据清洗使用 // 敬告二开开发者们,为了开源大模型的发展,请不要修改上述消息,此消息用于后续数据清洗使用
@ -157,44 +167,21 @@ export class ClientApi {
} }
} }
export const api = new ClientApi();
export function getAuthHeaders() {
const accessStore = useAccessStore.getState();
const headers: Record<string, string> = {};
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(apiKey)) {
headers[authHeader] = makeBearer(apiKey);
} else if (
accessStore.enabledAccessControl() &&
validString(accessStore.accessCode)
) {
headers[authHeader] = makeBearer(
ACCESS_CODE_PREFIX + accessStore.accessCode,
);
}
return headers;
}
export function getHeaders() { export function getHeaders() {
const accessStore = useAccessStore.getState(); const accessStore = useAccessStore.getState();
const headers: Record<string, string> = { const headers: Record<string, string> = {
"Content-Type": "application/json", "Content-Type": "application/json",
"x-requested-with": "XMLHttpRequest", "x-requested-with": "XMLHttpRequest",
}; };
const modelConfig = useChatStore.getState().currentSession().mask.modelConfig;
const isGoogle = modelConfig.model === "gemini-pro";
const isAzure = accessStore.provider === ServiceProvider.Azure; const isAzure = accessStore.provider === ServiceProvider.Azure;
const authHeader = isAzure ? "api-key" : "Authorization"; const authHeader = isAzure ? "api-key" : "Authorization";
const apiKey = isAzure ? accessStore.azureApiKey : accessStore.openaiApiKey; const apiKey = isGoogle
? accessStore.googleApiKey
: isAzure
? accessStore.azureApiKey
: accessStore.openaiApiKey;
const makeBearer = (s: string) => `${isAzure ? "" : "Bearer "}${s.trim()}`; const makeBearer = (s: string) => `${isAzure ? "" : "Bearer "}${s.trim()}`;
const validString = (x: string) => x && x.length > 0; const validString = (x: string) => x && x.length > 0;
@ -213,31 +200,3 @@ export function getHeaders() {
return headers; return headers;
} }
export function getGeminiHeaders() {
const accessStore = useAccessStore.getState();
const headers: Record<string, string> = {
"Content-Type": "application/json",
"x-requested-with": "XMLHttpRequest",
};
const authHeader = "Authorization";
const apiKey = accessStore.googleApiKey;
const makeBearer = (s: string) => `${"Bearer "}${s.trim()}`;
const validString = (x: string) => x && x.length > 0;
// use user's api key first
if (validString(apiKey)) {
headers[authHeader] = makeBearer(apiKey);
} else if (
accessStore.enabledAccessControl() &&
validString(accessStore.accessCode)
) {
headers[authHeader] = makeBearer(
ACCESS_CODE_PREFIX + accessStore.accessCode,
);
}
return headers;
}

View File

@ -1,120 +1,77 @@
import { import { Google, REQUEST_TIMEOUT_MS } from "@/app/constant";
ApiPath, import { ChatOptions, getHeaders, LLMApi, LLMModel, LLMUsage } from "../api";
DEFAULT_API_HOST,
DEFAULT_MODELS,
GooglePath,
OpenaiPath,
REQUEST_TIMEOUT_MS,
ServiceProvider,
} from "@/app/constant";
import { useAccessStore, useAppConfig, useChatStore } from "@/app/store"; import { useAccessStore, useAppConfig, useChatStore } from "@/app/store";
import {
AgentChatOptions,
ChatOptions,
getGeminiHeaders,
getHeaders,
LLMApi,
LLMModel,
LLMUsage,
} from "../api";
import Locale from "../../locales";
import { import {
EventStreamContentType, EventStreamContentType,
fetchEventSource, fetchEventSource,
} from "@fortaine/fetch-event-source"; } from "@fortaine/fetch-event-source";
import { prettyObject } from "@/app/utils/format"; import { prettyObject } from "@/app/utils/format";
import { getClientConfig } from "@/app/config/client";
export interface OpenAIListModelResponse { import Locale from "../../locales";
object: string; import { getServerSideConfig } from "@/app/config/server";
data: Array<{ export class GeminiProApi implements LLMApi {
id: string;
object: string;
root: string;
}>;
}
export class GeminiApi implements LLMApi {
private disableListModels = true;
path(path: string): string {
const accessStore = useAccessStore.getState();
let baseUrl = ApiPath.GoogleAI;
// if (baseUrl.length === 0) {
// const isApp = !!getClientConfig()?.isApp;
// baseUrl = isApp ? DEFAULT_API_HOST : ApiPath.OpenAI;
// }
// if (baseUrl.endsWith("/")) {
// baseUrl = baseUrl.slice(0, baseUrl.length - 1);
// }
// if (!baseUrl.startsWith("http") && !baseUrl.startsWith(ApiPath.OpenAI)) {
// baseUrl = "https://" + baseUrl;
// }
// if (isAzure) {
// path = makeAzurePath(path, accessStore.azureApiVersion);
// }
return [baseUrl, path].join("/");
}
extractMessage(res: any) { extractMessage(res: any) {
return res.choices?.at(0)?.message?.content ?? ""; console.log("[Response] gemini-pro response: ", res);
}
async chat(options: ChatOptions) { return (
const messages: any[] = []; res?.candidates?.at(0)?.content?.parts.at(0)?.text ||
console.log(options.messages); res?.error?.message ||
let systemPrompt = ""; ""
for (const v of options.messages) { );
if (v.role === "system") { }
// systemPrompt = v.content; async chat(options: ChatOptions): Promise<void> {
continue; const messages = options.messages.map((v) => ({
role: v.role.replace("assistant", "model").replace("system", "user"),
parts: [{ text: v.content }],
}));
// google requires that role in neighboring messages must not be the same
for (let i = 0; i < messages.length - 1; ) {
// Check if current and next item both have the role "model"
if (messages[i].role === messages[i + 1].role) {
// Concatenate the 'parts' of the current and next item
messages[i].parts = messages[i].parts.concat(messages[i + 1].parts);
// Remove the next item
messages.splice(i + 1, 1);
} else {
// Move to the next item
i++;
} }
let content = v.content;
if (systemPrompt !== "") {
content = `${systemPrompt}\n${content}`;
systemPrompt = "";
}
let message: {
role: string;
parts: { text: string }[];
} = {
role: v.role === "assistant" ? "model" : "user",
parts: [],
};
message.parts.push({
text: content,
});
messages.push(message);
} }
const modelConfig = {
...useAppConfig.getState().modelConfig,
...useChatStore.getState().currentSession().mask.modelConfig,
...{
model: options.config.model,
},
};
const requestPayload = { const requestPayload = {
contents: messages, contents: messages,
generationConfig: { generationConfig: {
temperature: 1.0, // stopSequences: [
maxOutputTokens: 8000, // "Title"
topP: 0.8, // ],
topK: 10, temperature: modelConfig.temperature,
maxOutputTokens: modelConfig.max_tokens,
topP: modelConfig.top_p,
// "topK": modelConfig.top_k,
}, },
}; };
console.log("[Request] gemini payload: ", requestPayload); console.log("[Request] google payload: ", requestPayload);
const shouldStream = true; // todo: support stream later
const shouldStream = false;
const controller = new AbortController(); const controller = new AbortController();
options.onController?.(controller); options.onController?.(controller);
try { try {
const chatPath = this.path( const chatPath = this.path(Google.ChatPath);
GooglePath.ChatPath.replace("{{model}}", options.config.model),
);
const chatPayload = { const chatPayload = {
method: "POST", method: "POST",
body: JSON.stringify(requestPayload), body: JSON.stringify(requestPayload),
signal: controller.signal, signal: controller.signal,
headers: getGeminiHeaders(), headers: getHeaders(),
}; };
// make a fetch request // make a fetch request
@ -122,7 +79,6 @@ export class GeminiApi implements LLMApi {
() => controller.abort(), () => controller.abort(),
REQUEST_TIMEOUT_MS, REQUEST_TIMEOUT_MS,
); );
if (shouldStream) { if (shouldStream) {
let responseText = ""; let responseText = "";
let remainText = ""; let remainText = "";
@ -158,13 +114,14 @@ export class GeminiApi implements LLMApi {
}; };
controller.signal.onabort = finish; controller.signal.onabort = finish;
fetchEventSource(chatPath, { fetchEventSource(chatPath, {
...chatPayload, ...chatPayload,
async onopen(res) { async onopen(res) {
clearTimeout(requestTimeoutId); clearTimeout(requestTimeoutId);
const contentType = res.headers.get("content-type"); const contentType = res.headers.get("content-type");
console.log( console.log(
"[Google] request response content type: ", "[OpenAI] request response content type: ",
contentType, contentType,
); );
@ -207,15 +164,13 @@ export class GeminiApi implements LLMApi {
const text = msg.data; const text = msg.data;
try { try {
const json = JSON.parse(text) as { const json = JSON.parse(text) as {
candidates: Array<{ choices: Array<{
content: { delta: {
parts: Array<{ content: string;
text: string;
}>;
}; };
}>; }>;
}; };
const delta = json.candidates[0]?.content?.parts[0]?.text; const delta = json.choices[0]?.delta?.content;
if (delta) { if (delta) {
remainText += delta; remainText += delta;
} }
@ -237,6 +192,16 @@ export class GeminiApi implements LLMApi {
clearTimeout(requestTimeoutId); clearTimeout(requestTimeoutId);
const resJson = await res.json(); const resJson = await res.json();
if (resJson?.promptFeedback?.blockReason) {
// being blocked
options.onError?.(
new Error(
"Message is being blocked for reason: " +
resJson.promptFeedback.blockReason,
),
);
}
const message = this.extractMessage(resJson); const message = this.extractMessage(resJson);
options.onFinish(message); options.onFinish(message);
} }
@ -245,249 +210,13 @@ export class GeminiApi implements LLMApi {
options.onError?.(e as Error); options.onError?.(e as Error);
} }
} }
usage(): Promise<LLMUsage> {
async toolAgentChat(options: AgentChatOptions) { throw new Error("Method not implemented.");
const messages = options.messages.map((v) => ({
role: v.role,
content: v.content,
}));
const modelConfig = {
...useAppConfig.getState().modelConfig,
...useChatStore.getState().currentSession().mask.modelConfig,
...{
model: options.config.model,
},
};
const 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,
baseUrl: useAccessStore.getState().openaiUrl,
maxIterations: options.agentConfig.maxIterations,
returnIntermediateSteps: options.agentConfig.returnIntermediateSteps,
useTools: options.agentConfig.useTools,
};
console.log("[Request] openai payload: ", requestPayload);
const shouldStream = true;
const controller = new AbortController();
options.onController?.(controller);
try {
let path = "/api/langchain/tool/agent/";
const enableNodeJSPlugin = !!process.env.NEXT_PUBLIC_ENABLE_NODEJS_PLUGIN;
path = enableNodeJSPlugin ? path + "nodejs" : path + "edge";
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,
);
console.log("shouldStream", shouldStream);
if (shouldStream) {
let responseText = "";
let finished = false;
const finish = () => {
if (!finished) {
options.onFinish(responseText);
finished = true;
}
};
controller.signal.onabort = finish;
fetchEventSource(path, {
...chatPayload,
async onopen(res) {
clearTimeout(requestTimeoutId);
const contentType = res.headers.get("content-type");
console.log(
"[OpenAI] request response content type: ",
contentType,
);
if (contentType?.startsWith("text/plain")) {
responseText = await res.clone().text();
return finish();
}
if (
!res.ok ||
!res.headers
.get("content-type")
?.startsWith(EventStreamContentType) ||
res.status !== 200
) {
const responseTexts = [responseText];
let extraInfo = await res.clone().text();
console.warn(`extraInfo: ${extraInfo}`);
// try {
// const resJson = await res.clone().json();
// extraInfo = prettyObject(resJson);
// } catch { }
if (res.status === 401) {
responseTexts.push(Locale.Error.Unauthorized);
}
if (extraInfo) {
responseTexts.push(extraInfo);
}
responseText = responseTexts.join("\n\n");
return finish();
}
},
onmessage(msg) {
let response = JSON.parse(msg.data);
if (!response.isSuccess) {
console.error("[Request]", msg.data);
responseText = msg.data;
throw Error(response.message);
}
if (msg.data === "[DONE]" || finished) {
return finish();
}
try {
if (response && !response.isToolMessage) {
responseText += response.message;
options.onUpdate?.(responseText, response.message);
} else {
options.onToolUpdate?.(response.toolName!, response.message);
}
} catch (e) {
console.error("[Request] parse error", response, msg);
}
},
onclose() {
finish();
},
onerror(e) {
options.onError?.(e);
throw e;
},
openWhenHidden: true,
});
} else {
const res = await fetch(path, chatPayload);
clearTimeout(requestTimeoutId);
const resJson = await res.json();
const message = this.extractMessage(resJson);
options.onFinish(message);
}
} catch (e) {
console.log("[Request] failed to make a chat reqeust", e);
options.onError?.(e as Error);
}
} }
async usage() {
const formatDate = (d: Date) =>
`${d.getFullYear()}-${(d.getMonth() + 1).toString().padStart(2, "0")}-${d
.getDate()
.toString()
.padStart(2, "0")}`;
const ONE_DAY = 1 * 24 * 60 * 60 * 1000;
const now = new Date();
const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);
const startDate = formatDate(startOfMonth);
const endDate = formatDate(new Date(Date.now() + ONE_DAY));
const [used, subs] = await Promise.all([
fetch(
this.path(
`${OpenaiPath.UsagePath}?start_date=${startDate}&end_date=${endDate}`,
),
{
method: "GET",
headers: getHeaders(),
},
),
fetch(this.path(OpenaiPath.SubsPath), {
method: "GET",
headers: getHeaders(),
}),
]);
if (used.status === 401) {
throw new Error(Locale.Error.Unauthorized);
}
if (!used.ok || !subs.ok) {
throw new Error("Failed to query usage from openai");
}
const response = (await used.json()) as {
total_usage?: number;
error?: {
type: string;
message: string;
};
};
const total = (await subs.json()) as {
hard_limit_usd?: number;
};
if (response.error && response.error.type) {
throw Error(response.error.message);
}
if (response.total_usage) {
response.total_usage = Math.round(response.total_usage) / 100;
}
if (total.hard_limit_usd) {
total.hard_limit_usd = Math.round(total.hard_limit_usd * 100) / 100;
}
return {
used: response.total_usage,
total: total.hard_limit_usd,
} as LLMUsage;
}
async models(): Promise<LLMModel[]> { async models(): Promise<LLMModel[]> {
if (this.disableListModels) { return [];
return DEFAULT_MODELS.slice(); }
} path(path: string): string {
return "/api/google/" + path;
const res = await fetch(this.path(OpenaiPath.ListModelPath), {
method: "GET",
headers: {
...getHeaders(),
},
});
const resJson = (await res.json()) as OpenAIListModelResponse;
const chatModels = resJson.data?.filter((m) => m.id.startsWith("gpt-"));
console.log("[Models]", chatModels);
if (!chatModels) {
return [];
}
return chatModels.map((m) => ({
name: m.id,
available: true,
}));
} }
} }
export { OpenaiPath };

View File

@ -512,6 +512,11 @@ export class ChatGPTApi implements LLMApi {
return chatModels.map((m) => ({ return chatModels.map((m) => ({
name: m.id, name: m.id,
available: true, available: true,
provider: {
id: "openai",
providerName: "OpenAI",
providerType: "openai",
},
})); }));
} }
} }

View File

@ -64,6 +64,17 @@ export function AuthPage() {
); );
}} }}
/> />
<input
className={styles["auth-input"]}
type="password"
placeholder={Locale.Settings.Access.Google.ApiKey.Placeholder}
value={accessStore.googleApiKey}
onChange={(e) => {
accessStore.update(
(access) => (access.googleApiKey = e.currentTarget.value),
);
}}
/>
</> </>
) : null} ) : null}

View File

@ -29,10 +29,11 @@ import NextImage from "next/image";
import { toBlob, toPng } from "html-to-image"; import { toBlob, toPng } from "html-to-image";
import { DEFAULT_MASK_AVATAR } from "../store/mask"; import { DEFAULT_MASK_AVATAR } from "../store/mask";
import { api } from "../client/api";
import { prettyObject } from "../utils/format"; import { prettyObject } from "../utils/format";
import { EXPORT_MESSAGE_CLASS_NAME } from "../constant"; import { EXPORT_MESSAGE_CLASS_NAME, ModelProvider } from "../constant";
import { getClientConfig } from "../config/client"; import { getClientConfig } from "../config/client";
import { ClientApi } from "../client/api";
const Markdown = dynamic(async () => (await import("./markdown")).Markdown, { const Markdown = dynamic(async () => (await import("./markdown")).Markdown, {
loading: () => <LoadingIcon />, loading: () => <LoadingIcon />,
@ -301,10 +302,17 @@ export function PreviewActions(props: {
}) { }) {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [shouldExport, setShouldExport] = useState(false); const [shouldExport, setShouldExport] = useState(false);
const config = useAppConfig();
const onRenderMsgs = (msgs: ChatMessage[]) => { const onRenderMsgs = (msgs: ChatMessage[]) => {
setShouldExport(false); setShouldExport(false);
var api: ClientApi;
if (config.modelConfig.model === "gemini-pro") {
api = new ClientApi(ModelProvider.GeminiPro);
} else {
api = new ClientApi(ModelProvider.GPT);
}
api api
.share(msgs) .share(msgs)
.then((res) => { .then((res) => {
@ -530,7 +538,7 @@ export function ImagePreviewer(props: {
</div> </div>
<div> <div>
<div className={styles["main-title"]}>ChatGPT Next Web</div> <div className={styles["main-title"]}>NextChat</div>
<div className={styles["sub-title"]}> <div className={styles["sub-title"]}>
github.com/Yidadaa/ChatGPT-Next-Web github.com/Yidadaa/ChatGPT-Next-Web
</div> </div>

View File

@ -12,7 +12,7 @@ import LoadingIcon from "../icons/three-dots.svg";
import { getCSSVar, useMobileScreen } from "../utils"; import { getCSSVar, useMobileScreen } from "../utils";
import dynamic from "next/dynamic"; import dynamic from "next/dynamic";
import { Path, SlotID } from "../constant"; import { ModelProvider, Path, SlotID } from "../constant";
import { ErrorBoundary } from "./error"; import { ErrorBoundary } from "./error";
import { getISOLang, getLang } from "../locales"; import { getISOLang, getLang } from "../locales";
@ -27,7 +27,7 @@ import { SideBar } from "./sidebar";
import { useAppConfig } from "../store/config"; import { useAppConfig } from "../store/config";
import { AuthPage } from "./auth"; import { AuthPage } from "./auth";
import { getClientConfig } from "../config/client"; import { getClientConfig } from "../config/client";
import { api } from "../client/api"; import { ClientApi } from "../client/api";
import { useAccessStore } from "../store"; import { useAccessStore } from "../store";
export function Loading(props: { noLogo?: boolean }) { export function Loading(props: { noLogo?: boolean }) {
@ -132,7 +132,8 @@ function Screen() {
const isHome = location.pathname === Path.Home; const isHome = location.pathname === Path.Home;
const isAuth = location.pathname === Path.Auth; const isAuth = location.pathname === Path.Auth;
const isMobileScreen = useMobileScreen(); const isMobileScreen = useMobileScreen();
const shouldTightBorder = getClientConfig()?.isApp || (config.tightBorder && !isMobileScreen); const shouldTightBorder =
getClientConfig()?.isApp || (config.tightBorder && !isMobileScreen);
useEffect(() => { useEffect(() => {
loadAsyncGoogleFont(); loadAsyncGoogleFont();
@ -174,6 +175,12 @@ function Screen() {
export function useLoadData() { export function useLoadData() {
const config = useAppConfig(); const config = useAppConfig();
var api: ClientApi;
if (config.modelConfig.model === "gemini-pro") {
api = new ClientApi(ModelProvider.GeminiPro);
} else {
api = new ClientApi(ModelProvider.GPT);
}
useEffect(() => { useEffect(() => {
(async () => { (async () => {
const models = await api.llm.models(); const models = await api.llm.models();

View File

@ -29,7 +29,7 @@ export function ModelConfigList(props: {
.filter((v) => v.available) .filter((v) => v.available)
.map((v, i) => ( .map((v, i) => (
<option value={v.name} key={i}> <option value={v.name} key={i}>
{v.displayName} {v.displayName}({v.provider?.providerName})
</option> </option>
))} ))}
</Select> </Select>
@ -91,79 +91,84 @@ export function ModelConfigList(props: {
} }
></input> ></input>
</ListItem> </ListItem>
<ListItem
title={Locale.Settings.PresencePenalty.Title}
subTitle={Locale.Settings.PresencePenalty.SubTitle}
>
<InputRange
value={props.modelConfig.presence_penalty?.toFixed(1)}
min="-2"
max="2"
step="0.1"
onChange={(e) => {
props.updateConfig(
(config) =>
(config.presence_penalty =
ModalConfigValidator.presence_penalty(
e.currentTarget.valueAsNumber,
)),
);
}}
></InputRange>
</ListItem>
<ListItem {props.modelConfig.model === "gemini-pro" ? null : (
title={Locale.Settings.FrequencyPenalty.Title} <>
subTitle={Locale.Settings.FrequencyPenalty.SubTitle} <ListItem
> title={Locale.Settings.PresencePenalty.Title}
<InputRange subTitle={Locale.Settings.PresencePenalty.SubTitle}
value={props.modelConfig.frequency_penalty?.toFixed(1)} >
min="-2" <InputRange
max="2" value={props.modelConfig.presence_penalty?.toFixed(1)}
step="0.1" min="-2"
onChange={(e) => { max="2"
props.updateConfig( step="0.1"
(config) => onChange={(e) => {
(config.frequency_penalty = props.updateConfig(
ModalConfigValidator.frequency_penalty( (config) =>
e.currentTarget.valueAsNumber, (config.presence_penalty =
)), ModalConfigValidator.presence_penalty(
); e.currentTarget.valueAsNumber,
}} )),
></InputRange> );
</ListItem> }}
></InputRange>
</ListItem>
<ListItem <ListItem
title={Locale.Settings.InjectSystemPrompts.Title} title={Locale.Settings.FrequencyPenalty.Title}
subTitle={Locale.Settings.InjectSystemPrompts.SubTitle} subTitle={Locale.Settings.FrequencyPenalty.SubTitle}
> >
<input <InputRange
type="checkbox" value={props.modelConfig.frequency_penalty?.toFixed(1)}
checked={props.modelConfig.enableInjectSystemPrompts} min="-2"
onChange={(e) => max="2"
props.updateConfig( step="0.1"
(config) => onChange={(e) => {
(config.enableInjectSystemPrompts = e.currentTarget.checked), props.updateConfig(
) (config) =>
} (config.frequency_penalty =
></input> ModalConfigValidator.frequency_penalty(
</ListItem> e.currentTarget.valueAsNumber,
)),
);
}}
></InputRange>
</ListItem>
<ListItem <ListItem
title={Locale.Settings.InputTemplate.Title} title={Locale.Settings.InjectSystemPrompts.Title}
subTitle={Locale.Settings.InputTemplate.SubTitle} subTitle={Locale.Settings.InjectSystemPrompts.SubTitle}
> >
<input <input
type="text" type="checkbox"
value={props.modelConfig.template} checked={props.modelConfig.enableInjectSystemPrompts}
onChange={(e) => onChange={(e) =>
props.updateConfig( props.updateConfig(
(config) => (config.template = e.currentTarget.value), (config) =>
) (config.enableInjectSystemPrompts =
} e.currentTarget.checked),
></input> )
</ListItem> }
></input>
</ListItem>
<ListItem
title={Locale.Settings.InputTemplate.Title}
subTitle={Locale.Settings.InputTemplate.SubTitle}
>
<input
type="text"
value={props.modelConfig.template}
onChange={(e) =>
props.updateConfig(
(config) => (config.template = e.currentTarget.value),
)
}
></input>
</ListItem>
</>
)}
<ListItem <ListItem
title={Locale.Settings.HistoryCount.Title} title={Locale.Settings.HistoryCount.Title}
subTitle={Locale.Settings.HistoryCount.SubTitle} subTitle={Locale.Settings.HistoryCount.SubTitle}

View File

@ -52,6 +52,7 @@ import { copyToClipboard } from "../utils";
import Link from "next/link"; import Link from "next/link";
import { import {
Azure, Azure,
Google,
OPENAI_BASE_URL, OPENAI_BASE_URL,
Path, Path,
RELEASE_URL, RELEASE_URL,
@ -584,6 +585,7 @@ export function Settings() {
const accessStore = useAccessStore(); const accessStore = useAccessStore();
const shouldHideBalanceQuery = useMemo(() => { const shouldHideBalanceQuery = useMemo(() => {
const isOpenAiUrl = accessStore.openaiUrl.includes(OPENAI_BASE_URL); const isOpenAiUrl = accessStore.openaiUrl.includes(OPENAI_BASE_URL);
return ( return (
accessStore.hideBalanceQuery || accessStore.hideBalanceQuery ||
isOpenAiUrl || isOpenAiUrl ||
@ -999,7 +1001,7 @@ export function Settings() {
/> />
</ListItem> </ListItem>
</> </>
) : ( ) : accessStore.provider === "Azure" ? (
<> <>
<ListItem <ListItem
title={Locale.Settings.Access.Azure.Endpoint.Title} title={Locale.Settings.Access.Azure.Endpoint.Title}
@ -1058,7 +1060,66 @@ export function Settings() {
></input> ></input>
</ListItem> </ListItem>
</> </>
)} ) : accessStore.provider === "Google" ? (
<>
<ListItem
title={Locale.Settings.Access.Google.Endpoint.Title}
subTitle={
Locale.Settings.Access.Google.Endpoint.SubTitle +
Google.ExampleEndpoint
}
>
<input
type="text"
value={accessStore.googleUrl}
placeholder={Google.ExampleEndpoint}
onChange={(e) =>
accessStore.update(
(access) =>
(access.googleUrl = e.currentTarget.value),
)
}
></input>
</ListItem>
<ListItem
title={Locale.Settings.Access.Azure.ApiKey.Title}
subTitle={Locale.Settings.Access.Azure.ApiKey.SubTitle}
>
<PasswordInput
value={accessStore.googleApiKey}
type="text"
placeholder={
Locale.Settings.Access.Google.ApiKey.Placeholder
}
onChange={(e) => {
accessStore.update(
(access) =>
(access.googleApiKey = e.currentTarget.value),
);
}}
/>
</ListItem>
<ListItem
title={Locale.Settings.Access.Google.ApiVerion.Title}
subTitle={
Locale.Settings.Access.Google.ApiVerion.SubTitle
}
>
<input
type="text"
value={accessStore.googleApiVersion}
placeholder="2023-08-01-preview"
onChange={(e) =>
accessStore.update(
(access) =>
(access.googleApiVersion =
e.currentTarget.value),
)
}
></input>
</ListItem>
</>
) : null}
</> </>
)} )}
</> </>

View File

@ -155,7 +155,7 @@ export function SideBar(props: { className?: string }) {
> >
<div className={styles["sidebar-header"]} data-tauri-drag-region> <div className={styles["sidebar-header"]} data-tauri-drag-region>
<div className={styles["sidebar-title"]} data-tauri-drag-region> <div className={styles["sidebar-title"]} data-tauri-drag-region>
ChatGPT Next NextChat
</div> </div>
<div className={styles["sidebar-sub-title"]}> <div className={styles["sidebar-sub-title"]}>
Build your own AI assistant. Build your own AI assistant.

View File

@ -26,6 +26,10 @@ declare global {
AZURE_URL?: string; // https://{azure-url}/openai/deployments/{deploy-name} AZURE_URL?: string; // https://{azure-url}/openai/deployments/{deploy-name}
AZURE_API_KEY?: string; AZURE_API_KEY?: string;
AZURE_API_VERSION?: string; AZURE_API_VERSION?: string;
// google only
GOOGLE_API_KEY?: string;
GOOGLE_URL?: string;
} }
} }
} }
@ -61,6 +65,7 @@ export const getServerSideConfig = () => {
} }
const isAzure = !!process.env.AZURE_URL; const isAzure = !!process.env.AZURE_URL;
const isGoogle = !!process.env.GOOGLE_API_KEY;
const apiKeyEnvVar = process.env.OPENAI_API_KEY ?? ""; const apiKeyEnvVar = process.env.OPENAI_API_KEY ?? "";
const apiKeys = apiKeyEnvVar.split(",").map((v) => v.trim()); const apiKeys = apiKeyEnvVar.split(",").map((v) => v.trim());
@ -80,8 +85,9 @@ export const getServerSideConfig = () => {
azureApiKey: process.env.AZURE_API_KEY, azureApiKey: process.env.AZURE_API_KEY,
azureApiVersion: process.env.AZURE_API_VERSION, azureApiVersion: process.env.AZURE_API_VERSION,
googleApiKey: process.env.GOOGLE_API_KEY ?? "", isGoogle,
googleBaseUrl: process.env.GOOGLE_BASE_URL, googleApiKey: process.env.GOOGLE_API_KEY,
googleUrl: process.env.GOOGLE_URL,
needCode: ACCESS_CODES.size > 0, needCode: ACCESS_CODES.size > 0,
code: process.env.CODE, code: process.env.CODE,

View File

@ -13,6 +13,8 @@ export const DEFAULT_API_HOST = `${DEFAULT_CORS_HOST}/api/proxy`;
export const OPENAI_BASE_URL = "https://api.openai.com"; export const OPENAI_BASE_URL = "https://api.openai.com";
export const GOOGLE_BASE_URL = "https://generativelanguage.googleapis.com"; export const GOOGLE_BASE_URL = "https://generativelanguage.googleapis.com";
export const GEMINI_BASE_URL = "https://generativelanguage.googleapis.com/";
export enum Path { export enum Path {
Home = "/", Home = "/",
Chat = "/chat", Chat = "/chat",
@ -71,6 +73,12 @@ export const EXPORT_MESSAGE_CLASS_NAME = "export-markdown";
export enum ServiceProvider { export enum ServiceProvider {
OpenAI = "OpenAI", OpenAI = "OpenAI",
Azure = "Azure", Azure = "Azure",
Google = "Google",
}
export enum ModelProvider {
GPT = "GPT",
GeminiPro = "GeminiPro",
} }
export const OpenaiPath = { export const OpenaiPath = {
@ -89,6 +97,14 @@ export const Azure = {
ExampleEndpoint: "https://{resource-url}/openai/deployments/{deploy-id}", ExampleEndpoint: "https://{resource-url}/openai/deployments/{deploy-id}",
}; };
export const Google = {
ExampleEndpoint:
"https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent",
ChatPath: "v1beta/models/gemini-pro:generateContent",
// /api/openai/v1/chat/completions
};
export const DEFAULT_INPUT_TEMPLATE = `{{input}}`; // input / time / model / lang export const DEFAULT_INPUT_TEMPLATE = `{{input}}`; // input / time / model / lang
export const DEFAULT_SYSTEM_TEMPLATE = ` export const DEFAULT_SYSTEM_TEMPLATE = `
You are ChatGPT, a large language model trained by OpenAI. You are ChatGPT, a large language model trained by OpenAI.
@ -111,46 +127,110 @@ export const DEFAULT_MODELS = [
{ {
name: "gpt-4", name: "gpt-4",
available: true, available: true,
provider: {
id: "openai",
providerName: "OpenAI",
providerType: "openai",
},
}, },
{ {
name: "gpt-4-0613", name: "gpt-4-0613",
available: true, available: true,
provider: {
id: "openai",
providerName: "OpenAI",
providerType: "openai",
},
}, },
{ {
name: "gpt-4-32k", name: "gpt-4-32k",
available: true, available: true,
provider: {
id: "openai",
providerName: "OpenAI",
providerType: "openai",
},
}, },
{ {
name: "gpt-4-32k-0613", name: "gpt-4-32k-0613",
available: true, available: true,
provider: {
id: "openai",
providerName: "OpenAI",
providerType: "openai",
},
}, },
{ {
name: "gpt-4-1106-preview", name: "gpt-4-1106-preview",
available: true, available: true,
provider: {
id: "openai",
providerName: "OpenAI",
providerType: "openai",
},
}, },
{ {
name: "gpt-4-vision-preview", name: "gpt-4-vision-preview",
available: true, available: true,
provider: {
id: "openai",
providerName: "OpenAI",
providerType: "openai",
},
}, },
{ {
name: "gpt-3.5-turbo", name: "gpt-3.5-turbo",
available: true, available: true,
provider: {
id: "openai",
providerName: "OpenAI",
providerType: "openai",
},
}, },
{ {
name: "gpt-3.5-turbo-0613", name: "gpt-3.5-turbo-0613",
available: true, available: true,
provider: {
id: "openai",
providerName: "OpenAI",
providerType: "openai",
},
}, },
{ {
name: "gpt-3.5-turbo-1106", name: "gpt-3.5-turbo-1106",
available: true, available: true,
provider: {
id: "openai",
providerName: "OpenAI",
providerType: "openai",
},
}, },
{ {
name: "gpt-3.5-turbo-16k", name: "gpt-3.5-turbo-16k",
available: true, available: true,
provider: {
id: "openai",
providerName: "OpenAI",
providerType: "openai",
},
}, },
{ {
name: "gpt-3.5-turbo-16k-0613", name: "gpt-3.5-turbo-16k-0613",
available: true, available: true,
provider: {
id: "openai",
providerName: "OpenAI",
providerType: "openai",
},
},
{
name: "gemini-pro",
available: true,
provider: {
id: "google",
providerName: "Google",
providerType: "google",
},
}, },
] as const; ] as const;

View File

@ -6,7 +6,7 @@ import { getClientConfig } from "./config/client";
import { type Metadata } from "next"; import { type Metadata } from "next";
export const metadata: Metadata = { export const metadata: Metadata = {
title: "ChatGPT Next Web", title: "NextChat",
description: "Your personal ChatGPT Chat Bot.", description: "Your personal ChatGPT Chat Bot.",
viewport: { viewport: {
width: "device-width", width: "device-width",
@ -18,7 +18,7 @@ export const metadata: Metadata = {
{ media: "(prefers-color-scheme: dark)", color: "#151515" }, { media: "(prefers-color-scheme: dark)", color: "#151515" },
], ],
appleWebApp: { appleWebApp: {
title: "ChatGPT Next Web", title: "NextChat",
statusBarStyle: "default", statusBarStyle: "default",
}, },
}; };

View File

@ -13,7 +13,7 @@ const cn = {
Auth: { Auth: {
Title: "需要密码", Title: "需要密码",
Tips: "管理员开启了密码验证,请在下方填入访问码", Tips: "管理员开启了密码验证,请在下方填入访问码",
SubTips: "或者输入你的 OpenAI API 密钥", SubTips: "或者输入你的 OpenAI 或 Google API 密钥",
Input: "在此处填写访问码", Input: "在此处填写访问码",
Confirm: "确认", Confirm: "确认",
Later: "稍后再说", Later: "稍后再说",
@ -314,6 +314,23 @@ const cn = {
SubTitle: "选择指定的部分版本", SubTitle: "选择指定的部分版本",
}, },
}, },
Google: {
ApiKey: {
Title: "接口密钥",
SubTitle: "使用自定义 Google AI Studio API Key 绕过密码访问限制",
Placeholder: "Google AI Studio API Key",
},
Endpoint: {
Title: "接口地址",
SubTitle: "样例:",
},
ApiVerion: {
Title: "接口版本 (gemini-pro api version)",
SubTitle: "选择指定的部分版本",
},
},
CustomModel: { CustomModel: {
Title: "自定义模型名", Title: "自定义模型名",
SubTitle: "增加自定义模型可选项,使用英文逗号隔开", SubTitle: "增加自定义模型可选项,使用英文逗号隔开",
@ -363,7 +380,7 @@ const cn = {
Prompt: { Prompt: {
History: (content: string) => "这是历史聊天总结作为前情提要:" + content, History: (content: string) => "这是历史聊天总结作为前情提要:" + content,
Topic: Topic:
"使用四到五个字直接返回这句话的简要主题,不要解释、不要标点、不要语气词、不要多余文本,如果没有主题,请直接返回“闲聊”", "使用四到五个字直接返回这句话的简要主题,不要解释、不要标点、不要语气词、不要多余文本,不要加粗,如果没有主题,请直接返回“闲聊”",
Summarize: Summarize:
"简要总结一下对话内容,用作后续的上下文提示 prompt控制在 200 字以内", "简要总结一下对话内容,用作后续的上下文提示 prompt控制在 200 字以内",
}, },

View File

@ -15,7 +15,7 @@ const en: LocaleType = {
Auth: { Auth: {
Title: "Need Access Code", Title: "Need Access Code",
Tips: "Please enter access code below", Tips: "Please enter access code below",
SubTips: "Or enter your OpenAI API Key", SubTips: "Or enter your OpenAI or Google API Key",
Input: "access code", Input: "access code",
Confirm: "Confirm", Confirm: "Confirm",
Later: "Later", Later: "Later",
@ -321,6 +321,24 @@ const en: LocaleType = {
Title: "Custom Models", Title: "Custom Models",
SubTitle: "Custom model options, seperated by comma", SubTitle: "Custom model options, seperated by comma",
}, },
Google: {
ApiKey: {
Title: "API Key",
SubTitle:
"Bypass password access restrictions using a custom Google AI Studio API Key",
Placeholder: "Google AI Studio API Key",
},
Endpoint: {
Title: "Endpoint Address",
SubTitle: "Example:",
},
ApiVerion: {
Title: "API Version (gemini-pro api version)",
SubTitle: "Select a specific part version",
},
},
}, },
Model: "Model", Model: "Model",
@ -369,7 +387,7 @@ const en: LocaleType = {
History: (content: string) => History: (content: string) =>
"This is a summary of the chat history as a recap: " + content, "This is a summary of the chat history as a recap: " + content,
Topic: Topic:
"Please generate a four to five word title summarizing our conversation without any lead-in, punctuation, quotation marks, periods, symbols, or additional text. Remove enclosing quotation marks.", "Please generate a four to five word title summarizing our conversation without any lead-in, punctuation, quotation marks, periods, symbols, bold text, or additional text. Remove enclosing quotation marks.",
Summarize: Summarize:
"Summarize the discussion briefly in 200 words or less to use as a prompt for future context.", "Summarize the discussion briefly in 200 words or less to use as a prompt for future context.",
}, },

View File

@ -29,8 +29,10 @@ const DEFAULT_ACCESS_STATE = {
azureApiKey: "", azureApiKey: "",
azureApiVersion: "2023-08-01-preview", azureApiVersion: "2023-08-01-preview",
// google // google ai studio
googleUrl: "",
googleApiKey: "", googleApiKey: "",
googleApiVersion: "v1",
// server config // server config
needCode: true, needCode: true,
@ -59,6 +61,10 @@ export const useAccessStore = createPersistStore(
return ensure(get(), ["azureUrl", "azureApiKey", "azureApiVersion"]); return ensure(get(), ["azureUrl", "azureApiKey", "azureApiVersion"]);
}, },
isValidGoogle() {
return ensure(get(), ["googleApiKey"]);
},
isAuthorized() { isAuthorized() {
this.fetch(); this.fetch();
@ -66,6 +72,7 @@ export const useAccessStore = createPersistStore(
return ( return (
this.isValidOpenAI() || this.isValidOpenAI() ||
this.isValidAzure() || this.isValidAzure() ||
this.isValidGoogle() ||
!this.enabledAccessControl() || !this.enabledAccessControl() ||
(this.enabledAccessControl() && ensure(get(), ["accessCode"])) (this.enabledAccessControl() && ensure(get(), ["accessCode"]))
); );

View File

@ -8,10 +8,11 @@ import {
DEFAULT_INPUT_TEMPLATE, DEFAULT_INPUT_TEMPLATE,
DEFAULT_SYSTEM_TEMPLATE, DEFAULT_SYSTEM_TEMPLATE,
KnowledgeCutOffDate, KnowledgeCutOffDate,
ModelProvider,
StoreKey, StoreKey,
SUMMARIZE_MODEL, SUMMARIZE_MODEL,
} from "../constant"; } from "../constant";
import { api, RequestMessage } from "../client/api"; import { ClientApi, RequestMessage } from "../client/api";
import { ChatControllerPool } from "../client/controller"; import { ChatControllerPool } from "../client/controller";
import { prettyObject } from "../utils/format"; import { prettyObject } from "../utils/format";
import { estimateTokenLength } from "../utils/token"; import { estimateTokenLength } from "../utils/token";
@ -319,6 +320,8 @@ export const useChatStore = createPersistStore(
session.messages.push(savedUserMessage); session.messages.push(savedUserMessage);
session.messages.push(botMessage); session.messages.push(botMessage);
}); });
var api: ClientApi;
api = new ClientApi(ModelProvider.GPT);
if ( if (
config.pluginConfig.enable && config.pluginConfig.enable &&
session.mask.usePlugins && session.mask.usePlugins &&
@ -392,8 +395,10 @@ export const useChatStore = createPersistStore(
}, },
}); });
} else { } else {
if (modelConfig.model === "gemini-pro") {
api = new ClientApi(ModelProvider.GeminiPro);
}
// make request // make request
api.switch(modelConfig.model);
api.llm.chat({ api.llm.chat({
messages: sendMessages, messages: sendMessages,
config: { ...modelConfig, stream: true }, config: { ...modelConfig, stream: true },
@ -472,7 +477,9 @@ export const useChatStore = createPersistStore(
// system prompts, to get close to OpenAI Web ChatGPT // system prompts, to get close to OpenAI Web ChatGPT
const shouldInjectSystemPrompts = modelConfig.enableInjectSystemPrompts; const shouldInjectSystemPrompts = modelConfig.enableInjectSystemPrompts;
const systemPrompts = shouldInjectSystemPrompts
var systemPrompts: ChatMessage[] = [];
systemPrompts = shouldInjectSystemPrompts
? [ ? [
createMessage({ createMessage({
role: "system", role: "system",
@ -566,6 +573,14 @@ export const useChatStore = createPersistStore(
summarizeSession() { summarizeSession() {
const config = useAppConfig.getState(); const config = useAppConfig.getState();
const session = get().currentSession(); const session = get().currentSession();
const modelConfig = session.mask.modelConfig;
var api: ClientApi;
if (modelConfig.model === "gemini-pro") {
api = new ClientApi(ModelProvider.GeminiPro);
} else {
api = new ClientApi(ModelProvider.GPT);
}
// remove error messages if any // remove error messages if any
const messages = session.messages; const messages = session.messages;
@ -583,7 +598,6 @@ export const useChatStore = createPersistStore(
content: Locale.Store.Prompt.Topic, content: Locale.Store.Prompt.Topic,
}), }),
); );
api.switch(session.mask.modelConfig.model);
api.llm.chat({ api.llm.chat({
messages: topicMessages, messages: topicMessages,
config: { config: {
@ -598,8 +612,6 @@ export const useChatStore = createPersistStore(
}, },
}); });
} }
const modelConfig = session.mask.modelConfig;
const summarizeIndex = Math.max( const summarizeIndex = Math.max(
session.lastSummarizeIndex, session.lastSummarizeIndex,
session.clearContextIndex ?? 0, session.clearContextIndex ?? 0,
@ -633,7 +645,6 @@ export const useChatStore = createPersistStore(
historyMsgLength > modelConfig.compressMessageLengthThreshold && historyMsgLength > modelConfig.compressMessageLengthThreshold &&
modelConfig.sendMemory modelConfig.sendMemory
) { ) {
api.switch(modelConfig.model);
api.llm.chat({ api.llm.chat({
messages: toBeSummarizedMsgs.concat( messages: toBeSummarizedMsgs.concat(
createMessage({ createMessage({

View File

@ -1,9 +1,16 @@
import { FETCH_COMMIT_URL, FETCH_TAG_URL, StoreKey } from "../constant"; import {
import { api } from "../client/api"; FETCH_COMMIT_URL,
FETCH_TAG_URL,
ModelProvider,
StoreKey,
} from "../constant";
import { getClientConfig } from "../config/client"; import { getClientConfig } from "../config/client";
import { createPersistStore } from "../utils/store"; import { createPersistStore } from "../utils/store";
import ChatGptIcon from "../icons/chatgpt.png"; import ChatGptIcon from "../icons/chatgpt.png";
import Locale from "../locales"; import Locale from "../locales";
import { use } from "react";
import { useAppConfig } from ".";
import { ClientApi } from "../client/api";
const ONE_MINUTE = 60 * 1000; const ONE_MINUTE = 60 * 1000;
const isApp = !!getClientConfig()?.isApp; const isApp = !!getClientConfig()?.isApp;
@ -85,35 +92,40 @@ export const useUpdateStore = createPersistStore(
})); }));
if (window.__TAURI__?.notification && isApp) { if (window.__TAURI__?.notification && isApp) {
// Check if notification permission is granted // Check if notification permission is granted
await window.__TAURI__?.notification.isPermissionGranted().then((granted) => { await window.__TAURI__?.notification
if (!granted) { .isPermissionGranted()
return; .then((granted) => {
} else { if (!granted) {
// Request permission to show notifications return;
window.__TAURI__?.notification.requestPermission().then((permission) => { } else {
if (permission === 'granted') { // Request permission to show notifications
if (version === remoteId) { window.__TAURI__?.notification
// Show a notification using Tauri .requestPermission()
window.__TAURI__?.notification.sendNotification({ .then((permission) => {
title: "ChatGPT Next Web", if (permission === "granted") {
body: `${Locale.Settings.Update.IsLatest}`, if (version === remoteId) {
icon: `${ChatGptIcon.src}`, // Show a notification using Tauri
sound: "Default" window.__TAURI__?.notification.sendNotification({
}); title: "NextChat",
} else { body: `${Locale.Settings.Update.IsLatest}`,
const updateMessage = Locale.Settings.Update.FoundUpdate(`${remoteId}`); icon: `${ChatGptIcon.src}`,
// Show a notification for the new version using Tauri sound: "Default",
window.__TAURI__?.notification.sendNotification({ });
title: "ChatGPT Next Web", } else {
body: updateMessage, const updateMessage =
icon: `${ChatGptIcon.src}`, Locale.Settings.Update.FoundUpdate(`${remoteId}`);
sound: "Default" // Show a notification for the new version using Tauri
}); window.__TAURI__?.notification.sendNotification({
} title: "NextChat",
} body: updateMessage,
}); icon: `${ChatGptIcon.src}`,
} sound: "Default",
}); });
}
}
});
}
});
} }
console.log("[Got Upstream] ", remoteId); console.log("[Got Upstream] ", remoteId);
} catch (error) { } catch (error) {
@ -122,6 +134,7 @@ export const useUpdateStore = createPersistStore(
}, },
async updateUsage(force = false) { async updateUsage(force = false) {
// only support openai for now
const overOneMinute = Date.now() - get().lastUpdateUsage >= ONE_MINUTE; const overOneMinute = Date.now() - get().lastUpdateUsage >= ONE_MINUTE;
if (!overOneMinute && !force) return; if (!overOneMinute && !force) return;
@ -130,6 +143,7 @@ export const useUpdateStore = createPersistStore(
})); }));
try { try {
const api = new ClientApi(ModelProvider.GPT);
const usage = await api.llm.usage(); const usage = await api.llm.usage();
if (usage) { if (usage) {

View File

@ -6,23 +6,27 @@ export function collectModelTable(
) { ) {
const modelTable: Record< const modelTable: Record<
string, string,
{ available: boolean; name: string; displayName: string } {
available: boolean;
name: string;
displayName: string;
provider?: LLMModel["provider"]; // Marked as optional
}
> = {}; > = {};
// default models // default models
models.forEach( models.forEach((m) => {
(m) => modelTable[m.name] = {
(modelTable[m.name] = { ...m,
...m, displayName: m.name, // 'provider' is copied over if it exists
displayName: m.name, };
}), });
);
// server custom models // server custom models
customModels customModels
.split(",") .split(",")
.filter((v) => !!v && v.length > 0) .filter((v) => !!v && v.length > 0)
.map((m) => { .forEach((m) => {
const available = !m.startsWith("-"); const available = !m.startsWith("-");
const nameConfig = const nameConfig =
m.startsWith("+") || m.startsWith("-") ? m.slice(1) : m; m.startsWith("+") || m.startsWith("-") ? m.slice(1) : m;
@ -30,14 +34,17 @@ export function collectModelTable(
// enable or disable all models // enable or disable all models
if (name === "all") { if (name === "all") {
Object.values(modelTable).forEach((m) => (m.available = available)); Object.values(modelTable).forEach(
(model) => (model.available = available),
);
} else {
modelTable[name] = {
name,
displayName: displayName || name,
available,
provider: modelTable[name]?.provider, // Use optional chaining
};
} }
modelTable[name] = {
name,
displayName: displayName || name,
available,
};
}); });
return modelTable; return modelTable;
} }

View File

@ -1,13 +1,14 @@
version: "3.9" version: "3.9"
services: services:
chatgpt-next-web: chatgpt-next-web:
profiles: ["no-proxy"] profiles: [ "no-proxy" ]
container_name: chatgpt-next-web container_name: chatgpt-next-web
image: gosuto/chatgpt-next-web-langchain image: gosuto/chatgpt-next-web-langchain
ports: ports:
- 3000:3000 - 3000:3000
environment: environment:
- OPENAI_API_KEY=$OPENAI_API_KEY - OPENAI_API_KEY=$OPENAI_API_KEY
- GOOGLE_API_KEY=$GOOGLE_API_KEY
- CODE=$CODE - CODE=$CODE
- BASE_URL=$BASE_URL - BASE_URL=$BASE_URL
- OPENAI_ORG_ID=$OPENAI_ORG_ID - OPENAI_ORG_ID=$OPENAI_ORG_ID
@ -18,13 +19,14 @@ services:
- OPENAI_SB=$OPENAI_SB - OPENAI_SB=$OPENAI_SB
chatgpt-next-web-proxy: chatgpt-next-web-proxy:
profiles: ["proxy"] profiles: [ "proxy" ]
container_name: chatgpt-next-web-proxy container_name: chatgpt-next-web-proxy
image: gosuto/chatgpt-next-web-langchain image: gosuto/chatgpt-next-web-langchain
ports: ports:
- 3000:3000 - 3000:3000
environment: environment:
- OPENAI_API_KEY=$OPENAI_API_KEY - OPENAI_API_KEY=$OPENAI_API_KEY
- GOOGLE_API_KEY=$GOOGLE_API_KEY
- CODE=$CODE - CODE=$CODE
- PROXY_URL=$PROXY_URL - PROXY_URL=$PROXY_URL
- BASE_URL=$BASE_URL - BASE_URL=$BASE_URL

View File

@ -23,7 +23,7 @@ Docker 版本相当于稳定版latest Docker 总是与 latest release version
## 如何修改 Vercel 环境变量 ## 如何修改 Vercel 环境变量
- 进入 vercel 的控制台页面; - 进入 vercel 的控制台页面;
- 选中你的 chatgpt next web 项目; - 选中你的 NextChat 项目;
- 点击页面头部的 Settings 选项; - 点击页面头部的 Settings 选项;
- 找到侧边栏的 Environment Variables 选项; - 找到侧边栏的 Environment Variables 选项;
- 修改对应的值即可。 - 修改对应的值即可。

View File

@ -23,7 +23,7 @@ Docker 버전은 사실상 안정된 버전과 같습니다. latest Docker는
## Vercel 환경 변수를 어떻게 수정하나요? ## Vercel 환경 변수를 어떻게 수정하나요?
- Vercel의 제어판 페이지로 이동합니다. - Vercel의 제어판 페이지로 이동합니다.
- chatgpt next web 프로젝트를 선택합니다. - NextChat 프로젝트를 선택합니다.
- 페이지 상단의 Settings 옵션을 클릭합니다. - 페이지 상단의 Settings 옵션을 클릭합니다.
- 사이드바의 Environment Variables 옵션을 찾습니다. - 사이드바의 Environment Variables 옵션을 찾습니다.
- 해당 값을 수정합니다. - 해당 값을 수정합니다.

View File

@ -2,7 +2,7 @@
> No english version yet, please read this doc with ChatGPT or other translation tools. > No english version yet, please read this doc with ChatGPT or other translation tools.
本文档用于解释 ChatGPT Next Web 的部分功能介绍和设计原则。 本文档用于解释 NextChat 的部分功能介绍和设计原则。
## 面具 (Mask) ## 面具 (Mask)
@ -22,7 +22,7 @@
编辑步骤如下: 编辑步骤如下:
1. 在 ChatGPT Next Web 中配置好一个面具; 1. 在 NextChat 中配置好一个面具;
2. 使用面具编辑页面的下载按钮,将面具保存为 JSON 格式; 2. 使用面具编辑页面的下载按钮,将面具保存为 JSON 格式;
3. 让 ChatGPT 帮你将 json 文件格式化为对应的 ts 代码; 3. 让 ChatGPT 帮你将 json 文件格式化为对应的 ts 代码;
4. 放入对应的 .ts 文件。 4. 放入对应的 .ts 文件。

View File

@ -19,7 +19,7 @@
"@aws-sdk/client-s3": "^3.414.0", "@aws-sdk/client-s3": "^3.414.0",
"@aws-sdk/s3-request-presigner": "^3.414.0", "@aws-sdk/s3-request-presigner": "^3.414.0",
"@fortaine/fetch-event-source": "^3.0.6", "@fortaine/fetch-event-source": "^3.0.6",
"@hello-pangea/dnd": "^16.3.0", "@hello-pangea/dnd": "^16.5.0",
"@svgr/webpack": "^6.5.1", "@svgr/webpack": "^6.5.1",
"@vercel/analytics": "^0.1.11", "@vercel/analytics": "^0.1.11",
"axios": "^1.4.0", "axios": "^1.4.0",
@ -27,8 +27,8 @@
"duck-duck-scrape": "^2.2.4", "duck-duck-scrape": "^2.2.4",
"emoji-picker-react": "^4.5.15", "emoji-picker-react": "^4.5.15",
"encoding": "^0.1.13", "encoding": "^0.1.13",
"fuse.js": "^6.6.2",
"html-entities": "^2.4.0", "html-entities": "^2.4.0",
"fuse.js": "^7.0.0",
"html-to-image": "^1.11.11", "html-to-image": "^1.11.11",
"html-to-text": "^9.0.5", "html-to-text": "^9.0.5",
"https-proxy-agent": "^7.0.2", "https-proxy-agent": "^7.0.2",
@ -54,8 +54,8 @@
"zustand": "^4.3.8" "zustand": "^4.3.8"
}, },
"devDependencies": { "devDependencies": {
"@tauri-apps/cli": "^1.4.0",
"@types/html-to-text": "^9.0.1", "@types/html-to-text": "^9.0.1",
"@tauri-apps/cli": "^1.5.8",
"@types/node": "^20.9.0", "@types/node": "^20.9.0",
"@types/react": "^18.2.14", "@types/react": "^18.2.14",
"@types/react-dom": "^18.2.7", "@types/react-dom": "^18.2.7",

View File

@ -1,21 +1,20 @@
{ {
"name": "ChatGPT Next Web", "name": "NextChat",
"short_name": "ChatGPT", "short_name": "NextChat",
"icons": [ "icons": [
{ {
"src": "/android-chrome-192x192.png", "src": "/android-chrome-192x192.png",
"sizes": "192x192", "sizes": "192x192",
"type": "image/png" "type": "image/png"
}, },
{ {
"src": "/android-chrome-512x512.png", "src": "/android-chrome-512x512.png",
"sizes": "512x512", "sizes": "512x512",
"type": "image/png" "type": "image/png"
} }
], ],
"start_url": "/", "start_url": "/",
"theme_color": "#ffffff", "theme_color": "#ffffff",
"background_color": "#ffffff", "background_color": "#ffffff",
"display": "standalone" "display": "standalone"
} }

View File

@ -8,7 +8,7 @@
"withGlobalTauri": true "withGlobalTauri": true
}, },
"package": { "package": {
"productName": "ChatGPT Next Web", "productName": "NextChat",
"version": "2.9.13" "version": "2.9.13"
}, },
"tauri": { "tauri": {
@ -68,7 +68,7 @@
"icons/icon.ico" "icons/icon.ico"
], ],
"identifier": "com.yida.chatgpt.next.web", "identifier": "com.yida.chatgpt.next.web",
"longDescription": "ChatGPT Next Web is a cross-platform ChatGPT client, including Web/Win/Linux/OSX/PWA.", "longDescription": "NextChat is a cross-platform ChatGPT client, including Web/Win/Linux/OSX/PWA.",
"macOS": { "macOS": {
"entitlements": null, "entitlements": null,
"exceptionDomain": "", "exceptionDomain": "",
@ -77,7 +77,7 @@
"signingIdentity": null "signingIdentity": null
}, },
"resources": [], "resources": [],
"shortDescription": "ChatGPT Next Web App", "shortDescription": "NextChat App",
"targets": "all", "targets": "all",
"windows": { "windows": {
"certificateThumbprint": null, "certificateThumbprint": null,
@ -104,11 +104,11 @@
"fullscreen": false, "fullscreen": false,
"height": 600, "height": 600,
"resizable": true, "resizable": true,
"title": "ChatGPT Next Web", "title": "NextChat",
"width": 960, "width": 960,
"hiddenTitle": true, "hiddenTitle": true,
"titleBarStyle": "Overlay" "titleBarStyle": "Overlay"
} }
] ]
} }
} }

267
yarn.lock
View File

@ -617,6 +617,14 @@
dependencies: dependencies:
"@babel/highlight" "^7.18.6" "@babel/highlight" "^7.18.6"
"@babel/code-frame@^7.22.13":
version "7.22.13"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.13.tgz#e3c1c099402598483b7a8c46a721d1038803755e"
integrity sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==
dependencies:
"@babel/highlight" "^7.22.13"
chalk "^2.4.2"
"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.20.1", "@babel/compat-data@^7.20.5": "@babel/compat-data@^7.17.7", "@babel/compat-data@^7.20.1", "@babel/compat-data@^7.20.5":
version "7.21.0" version "7.21.0"
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.21.0.tgz#c241dc454e5b5917e40d37e525e2f4530c399298" resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.21.0.tgz#c241dc454e5b5917e40d37e525e2f4530c399298"
@ -653,6 +661,16 @@
"@jridgewell/trace-mapping" "^0.3.17" "@jridgewell/trace-mapping" "^0.3.17"
jsesc "^2.5.1" jsesc "^2.5.1"
"@babel/generator@^7.23.0":
version "7.23.0"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.0.tgz#df5c386e2218be505b34837acbcb874d7a983420"
integrity sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==
dependencies:
"@babel/types" "^7.23.0"
"@jridgewell/gen-mapping" "^0.3.2"
"@jridgewell/trace-mapping" "^0.3.17"
jsesc "^2.5.1"
"@babel/helper-annotate-as-pure@^7.18.6": "@babel/helper-annotate-as-pure@^7.18.6":
version "7.18.6" version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz#eaa49f6f80d5a33f9a5dd2276e6d6e451be0a6bb" resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz#eaa49f6f80d5a33f9a5dd2276e6d6e451be0a6bb"
@ -718,6 +736,11 @@
resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz#0c0cee9b35d2ca190478756865bb3528422f51be" resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz#0c0cee9b35d2ca190478756865bb3528422f51be"
integrity sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg== integrity sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==
"@babel/helper-environment-visitor@^7.22.20":
version "7.22.20"
resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167"
integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==
"@babel/helper-explode-assignable-expression@^7.18.6": "@babel/helper-explode-assignable-expression@^7.18.6":
version "7.18.6" version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz#41f8228ef0a6f1a036b8dfdfec7ce94f9a6bc096" resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz#41f8228ef0a6f1a036b8dfdfec7ce94f9a6bc096"
@ -733,6 +756,14 @@
"@babel/template" "^7.20.7" "@babel/template" "^7.20.7"
"@babel/types" "^7.21.0" "@babel/types" "^7.21.0"
"@babel/helper-function-name@^7.23.0":
version "7.23.0"
resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759"
integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==
dependencies:
"@babel/template" "^7.22.15"
"@babel/types" "^7.23.0"
"@babel/helper-hoist-variables@^7.18.6": "@babel/helper-hoist-variables@^7.18.6":
version "7.18.6" version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz#d4d2c8fb4baeaa5c68b99cc8245c56554f926678" resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz#d4d2c8fb4baeaa5c68b99cc8245c56554f926678"
@ -740,6 +771,13 @@
dependencies: dependencies:
"@babel/types" "^7.18.6" "@babel/types" "^7.18.6"
"@babel/helper-hoist-variables@^7.22.5":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb"
integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==
dependencies:
"@babel/types" "^7.22.5"
"@babel/helper-member-expression-to-functions@^7.20.7", "@babel/helper-member-expression-to-functions@^7.21.0": "@babel/helper-member-expression-to-functions@^7.20.7", "@babel/helper-member-expression-to-functions@^7.21.0":
version "7.21.0" version "7.21.0"
resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.21.0.tgz#319c6a940431a133897148515877d2f3269c3ba5" resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.21.0.tgz#319c6a940431a133897148515877d2f3269c3ba5"
@ -823,16 +861,33 @@
dependencies: dependencies:
"@babel/types" "^7.18.6" "@babel/types" "^7.18.6"
"@babel/helper-split-export-declaration@^7.22.6":
version "7.22.6"
resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c"
integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==
dependencies:
"@babel/types" "^7.22.5"
"@babel/helper-string-parser@^7.19.4": "@babel/helper-string-parser@^7.19.4":
version "7.19.4" version "7.19.4"
resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz#38d3acb654b4701a9b77fb0615a96f775c3a9e63" resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz#38d3acb654b4701a9b77fb0615a96f775c3a9e63"
integrity sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw== integrity sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==
"@babel/helper-string-parser@^7.22.5":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f"
integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==
"@babel/helper-validator-identifier@^7.18.6", "@babel/helper-validator-identifier@^7.19.1": "@babel/helper-validator-identifier@^7.18.6", "@babel/helper-validator-identifier@^7.19.1":
version "7.19.1" version "7.19.1"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2"
integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w== integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==
"@babel/helper-validator-identifier@^7.22.20":
version "7.22.20"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0"
integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==
"@babel/helper-validator-option@^7.18.6", "@babel/helper-validator-option@^7.21.0": "@babel/helper-validator-option@^7.18.6", "@babel/helper-validator-option@^7.21.0":
version "7.21.0" version "7.21.0"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz#8224c7e13ace4bafdc4004da2cf064ef42673180" resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz#8224c7e13ace4bafdc4004da2cf064ef42673180"
@ -866,11 +921,25 @@
chalk "^2.0.0" chalk "^2.0.0"
js-tokens "^4.0.0" js-tokens "^4.0.0"
"@babel/highlight@^7.22.13":
version "7.22.20"
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.20.tgz#4ca92b71d80554b01427815e06f2df965b9c1f54"
integrity sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==
dependencies:
"@babel/helper-validator-identifier" "^7.22.20"
chalk "^2.4.2"
js-tokens "^4.0.0"
"@babel/parser@^7.20.7", "@babel/parser@^7.21.3": "@babel/parser@^7.20.7", "@babel/parser@^7.21.3":
version "7.21.3" version "7.21.3"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.21.3.tgz#1d285d67a19162ff9daa358d4cb41d50c06220b3" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.21.3.tgz#1d285d67a19162ff9daa358d4cb41d50c06220b3"
integrity sha512-lobG0d7aOfQRXh8AyklEAgZGvA4FShxo6xQbUrrT/cNBPUdIDojlokwJsQyCC/eKia7ifqM0yP+2DRZ4WKw2RQ== integrity sha512-lobG0d7aOfQRXh8AyklEAgZGvA4FShxo6xQbUrrT/cNBPUdIDojlokwJsQyCC/eKia7ifqM0yP+2DRZ4WKw2RQ==
"@babel/parser@^7.22.15", "@babel/parser@^7.23.0":
version "7.23.0"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.0.tgz#da950e622420bf96ca0d0f2909cdddac3acd8719"
integrity sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6": "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6":
version "7.18.6" version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz#da5b8f9a580acdfbe53494dba45ea389fb09a4d2" resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz#da5b8f9a580acdfbe53494dba45ea389fb09a4d2"
@ -1554,12 +1623,12 @@
resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310" resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310"
integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==
"@babel/runtime@^7.12.1", "@babel/runtime@^7.20.7", "@babel/runtime@^7.22.5", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2": "@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.22.5" version "7.23.6"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.5.tgz#8564dd588182ce0047d55d7a75e93921107b57ec" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.6.tgz#c05e610dc228855dc92ef1b53d07389ed8ab521d"
integrity sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA== integrity sha512-zHd0eUrf5GZoOWVCXp6koAKQTfZV07eit6bGPmJgnZdnSAvvZee6zniW2XMF7Cmc4ISOOnPy3QaSiIJGJkVEDQ==
dependencies: dependencies:
regenerator-runtime "^0.13.11" regenerator-runtime "^0.14.0"
"@babel/template@^7.18.10", "@babel/template@^7.20.7": "@babel/template@^7.18.10", "@babel/template@^7.20.7":
version "7.20.7" version "7.20.7"
@ -1570,19 +1639,28 @@
"@babel/parser" "^7.20.7" "@babel/parser" "^7.20.7"
"@babel/types" "^7.20.7" "@babel/types" "^7.20.7"
"@babel/traverse@^7.20.5", "@babel/traverse@^7.20.7", "@babel/traverse@^7.21.0", "@babel/traverse@^7.21.2", "@babel/traverse@^7.21.3": "@babel/template@^7.22.15":
version "7.21.3" version "7.22.15"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.21.3.tgz#4747c5e7903d224be71f90788b06798331896f67" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38"
integrity sha512-XLyopNeaTancVitYZe2MlUEvgKb6YVVPXzofHgqHijCImG33b/uTurMS488ht/Hbsb2XK3U2BnSTxKVNGV3nGQ== integrity sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==
dependencies: dependencies:
"@babel/code-frame" "^7.18.6" "@babel/code-frame" "^7.22.13"
"@babel/generator" "^7.21.3" "@babel/parser" "^7.22.15"
"@babel/helper-environment-visitor" "^7.18.9" "@babel/types" "^7.22.15"
"@babel/helper-function-name" "^7.21.0"
"@babel/helper-hoist-variables" "^7.18.6" "@babel/traverse@^7.20.5", "@babel/traverse@^7.20.7", "@babel/traverse@^7.21.0", "@babel/traverse@^7.21.2", "@babel/traverse@^7.21.3":
"@babel/helper-split-export-declaration" "^7.18.6" version "7.23.2"
"@babel/parser" "^7.21.3" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.2.tgz#329c7a06735e144a506bdb2cad0268b7f46f4ad8"
"@babel/types" "^7.21.3" integrity sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==
dependencies:
"@babel/code-frame" "^7.22.13"
"@babel/generator" "^7.23.0"
"@babel/helper-environment-visitor" "^7.22.20"
"@babel/helper-function-name" "^7.23.0"
"@babel/helper-hoist-variables" "^7.22.5"
"@babel/helper-split-export-declaration" "^7.22.6"
"@babel/parser" "^7.23.0"
"@babel/types" "^7.23.0"
debug "^4.1.0" debug "^4.1.0"
globals "^11.1.0" globals "^11.1.0"
@ -1595,6 +1673,15 @@
"@babel/helper-validator-identifier" "^7.19.1" "@babel/helper-validator-identifier" "^7.19.1"
to-fast-properties "^2.0.0" to-fast-properties "^2.0.0"
"@babel/types@^7.22.15", "@babel/types@^7.22.5", "@babel/types@^7.23.0":
version "7.23.0"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.0.tgz#8c1f020c9df0e737e4e247c0619f58c68458aaeb"
integrity sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==
dependencies:
"@babel/helper-string-parser" "^7.22.5"
"@babel/helper-validator-identifier" "^7.22.20"
to-fast-properties "^2.0.0"
"@braintree/sanitize-url@^6.0.1": "@braintree/sanitize-url@^6.0.1":
version "6.0.4" version "6.0.4"
resolved "https://registry.yarnpkg.com/@braintree/sanitize-url/-/sanitize-url-6.0.4.tgz#923ca57e173c6b232bbbb07347b1be982f03e783" resolved "https://registry.yarnpkg.com/@braintree/sanitize-url/-/sanitize-url-6.0.4.tgz#923ca57e173c6b232bbbb07347b1be982f03e783"
@ -1637,16 +1724,16 @@
resolved "https://registry.npmmirror.com/@fortaine/fetch-event-source/-/fetch-event-source-3.0.6.tgz#b8552a2ca2c5202f5699b93a92be0188d422b06e" resolved "https://registry.npmmirror.com/@fortaine/fetch-event-source/-/fetch-event-source-3.0.6.tgz#b8552a2ca2c5202f5699b93a92be0188d422b06e"
integrity sha512-621GAuLMvKtyZQ3IA6nlDWhV1V/7PGOTNIGLUifxt0KzM+dZIweJ6F3XvQF3QnqeNfS1N7WQ0Kil1Di/lhChEw== integrity sha512-621GAuLMvKtyZQ3IA6nlDWhV1V/7PGOTNIGLUifxt0KzM+dZIweJ6F3XvQF3QnqeNfS1N7WQ0Kil1Di/lhChEw==
"@hello-pangea/dnd@^16.3.0": "@hello-pangea/dnd@^16.5.0":
version "16.3.0" version "16.5.0"
resolved "https://registry.yarnpkg.com/@hello-pangea/dnd/-/dnd-16.3.0.tgz#3776212f812df4e8e69c42831ec8ab7ff3a087d6" resolved "https://registry.yarnpkg.com/@hello-pangea/dnd/-/dnd-16.5.0.tgz#f323ff9f813204818bc67648a383e8715f47c59c"
integrity sha512-RYQ/K8shtJoyNPvFWz0gfXIK7HF3P3mL9UZFGMuHB0ljRSXVgMjVFI/FxcZmakMzw6tO7NflWLriwTNBow/4vw== integrity sha512-n+am6O32jo/CFXciCysz83lPM3I3F58FJw4uS44TceieymcyxQSfzK5OhzPAKrVBZktmuOI6Zim9WABTMtXv4A==
dependencies: dependencies:
"@babel/runtime" "^7.22.5" "@babel/runtime" "^7.23.2"
css-box-model "^1.2.1" css-box-model "^1.2.1"
memoize-one "^6.0.0" memoize-one "^6.0.0"
raf-schd "^4.0.3" raf-schd "^4.0.3"
react-redux "^8.1.1" react-redux "^8.1.3"
redux "^4.2.1" redux "^4.2.1"
use-memo-one "^1.1.3" use-memo-one "^1.1.3"
@ -2422,71 +2509,71 @@
dependencies: dependencies:
tslib "^2.4.0" tslib "^2.4.0"
"@tauri-apps/cli-darwin-arm64@1.4.0": "@tauri-apps/cli-darwin-arm64@1.5.8":
version "1.4.0" version "1.5.8"
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-1.4.0.tgz#e76bb8515ae31f03f2cbd440c1a09b237a79b3ac" resolved "https://registry.yarnpkg.com/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-1.5.8.tgz#28ca810b910979260dd77c92951d16340fcaa711"
integrity sha512-nA/ml0SfUt6/CYLVbHmT500Y+ijqsuv5+s9EBnVXYSLVg9kbPUZJJHluEYK+xKuOj6xzyuT/+rZFMRapmJD3jQ== integrity sha512-/AksDWfAt3NUSt8Rq2a3gTLASChKzldPVUjmJhcbtsuzFg2nx5g+hhOHxfBYzss2Te1K5mzlu+73LAMy1Sb9Gw==
"@tauri-apps/cli-darwin-x64@1.4.0": "@tauri-apps/cli-darwin-x64@1.5.8":
version "1.4.0" version "1.5.8"
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-1.4.0.tgz#dd1472460550d0aa0ec6e699b073be2d77e5b962" resolved "https://registry.yarnpkg.com/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-1.5.8.tgz#4060fb0ffcc8312cf48701df51e0e9b665f18382"
integrity sha512-ov/F6Zr+dg9B0PtRu65stFo2G0ow2TUlneqYYrkj+vA3n+moWDHfVty0raDjMLQbQt3rv3uayFMXGPMgble9OA== integrity sha512-gcfSh+BFRDdbIGpggZ1+5R5SgToz2A9LthH8P4ak3OHagDzDvI6ov6zy2UQE3XDWJKdnlna2rSR1dIuRZ0T9bA==
"@tauri-apps/cli-linux-arm-gnueabihf@1.4.0": "@tauri-apps/cli-linux-arm-gnueabihf@1.5.8":
version "1.4.0" version "1.5.8"
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-1.4.0.tgz#325e90e47d260ba71a499850ce769b5a6bdfd48d" resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-1.5.8.tgz#00256432520edf04004962caa92cd84fbcc8b63f"
integrity sha512-zwjbiMncycXDV7doovymyKD7sCg53ouAmfgpUqEBOTY3vgBi9TwijyPhJOqoG5vUVWhouNBC08akGmE4dja15g== integrity sha512-ZHQYuOBGvZubPnh5n8bNaN2VMxPBZWs26960FGQWamm9569UV/TNDHb6mD0Jjk9o0f9P+f98qNhuu5Y37P+vfQ==
"@tauri-apps/cli-linux-arm64-gnu@1.4.0": "@tauri-apps/cli-linux-arm64-gnu@1.5.8":
version "1.4.0" version "1.5.8"
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-1.4.0.tgz#b5d8f5cba3f8f7c7d44d071681f0ab0a37f2c46e" resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-1.5.8.tgz#7869571b06e8b36a072f2e0e7bb49baab9d3c868"
integrity sha512-5MCBcziqXC72mMXnkZU68mutXIR6zavDxopArE2gQtK841IlE06bIgtLi0kUUhlFJk2nhPRgiDgdLbrPlyt7fw== integrity sha512-FFs28Ew3R2EFPYKuyAIouTbp6YnR+shAmJGFNnVy7ibKHL0wxamVKqv1N5N9gUUr+EhbZu2syMBRfG9XQ5mgng==
"@tauri-apps/cli-linux-arm64-musl@1.4.0": "@tauri-apps/cli-linux-arm64-musl@1.5.8":
version "1.4.0" version "1.5.8"
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.4.0.tgz#f805ab2ee415875900f4b456f17dc4900d2a7911" resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.5.8.tgz#7cbe0395cbd09d4b49c945e36c2de99478c50a51"
integrity sha512-7J3pRB6n6uNYgIfCeKt2Oz8J7oSaz2s8GGFRRH2HPxuTHrBNCinzVYm68UhVpJrL3bnGkU0ziVZLsW/iaOGfUg== integrity sha512-dEYvNyLMmWD0jb30FNfVPXmBq6OGg6is3km+4RlGg8tZU5Zvq78ClUZtaZuER+N/hv27+Uc6UHl9X3hin8cGGw==
"@tauri-apps/cli-linux-x64-gnu@1.4.0": "@tauri-apps/cli-linux-x64-gnu@1.5.8":
version "1.4.0" version "1.5.8"
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-1.4.0.tgz#d3f5e69c22420c7ac9e4021b7a94bce2e48cb45d" resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-1.5.8.tgz#d03ba73f1ac68bf6bace7bf45b50e6b12ce4468b"
integrity sha512-Zh5gfAJxOv5AVWxcwuueaQ2vIAhlg0d6nZui6nMyfIJ8dbf3aZQ5ZzP38sYow5h/fbvgL+3GSQxZRBIa3c2E1w== integrity sha512-ut3TDbtLXmZhz6Q4wim57PV02wG+AfuLSWRPhTL9MsPsg/E7Y6sJhv0bIMAq6SwC59RCH52ZGft6RH7samV2NQ==
"@tauri-apps/cli-linux-x64-musl@1.4.0": "@tauri-apps/cli-linux-x64-musl@1.5.8":
version "1.4.0" version "1.5.8"
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-1.4.0.tgz#2e7f718272ffdd9ace80f57a35023ba0c74767ad" resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-1.5.8.tgz#4ce560aa102e9031d4c51c7bc853263cf3ab9616"
integrity sha512-OLAYoICU3FaYiTdBsI+lQTKnDHeMmFMXIApN0M+xGiOkoIOQcV9CConMPjgmJQ867+NHRNgUGlvBEAh9CiJodQ== integrity sha512-k6ei7ETXVZlNpFOhl/8Cnj709UbEr+VuY9xKK/HgwvNfjA5f8HQ9TSKk/Um7oeT1Y61/eEcvcgF/hDURhFJDPQ==
"@tauri-apps/cli-win32-arm64-msvc@1.4.0": "@tauri-apps/cli-win32-arm64-msvc@1.5.8":
version "1.4.0" version "1.5.8"
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-1.4.0.tgz#85cdb52a06feb92da785def4d02512099464525e" resolved "https://registry.yarnpkg.com/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-1.5.8.tgz#df83af81c6d89d4a505f2e96b3d443dd411c1a4a"
integrity sha512-gZ05GENFbI6CB5MlOUsLlU0kZ9UtHn9riYtSXKT6MYs8HSPRffPHaHSL0WxsJweWh9nR5Hgh/TUU8uW3sYCzCg== integrity sha512-l6zm31x1inkS2K5e7otUZ90XBoK+xr2KJObFCZbzmluBE+LM0fgIXCrj7xwH/f0RCUX3VY9HHx4EIo7eLGBXKQ==
"@tauri-apps/cli-win32-ia32-msvc@1.4.0": "@tauri-apps/cli-win32-ia32-msvc@1.5.8":
version "1.4.0" version "1.5.8"
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-1.4.0.tgz#0b7c921204058215aec9a5a00f735e73909bd330" resolved "https://registry.yarnpkg.com/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-1.5.8.tgz#92e5acc4dcd44aec88099059a04bb5ad3b4e59ff"
integrity sha512-JsetT/lTx/Zq98eo8T5CiRyF1nKeX04RO8JlJrI3ZOYsZpp/A5RJvMd/szQ17iOzwiHdge+tx7k2jHysR6oBlQ== integrity sha512-0k3YpWl6PKV4Qp2N52Sb45egXafSgQXcBaO7TIJG4EDfaEf5f6StN+hYSzdnrq9idrK5x9DDCPuebZTuJ+Q8EA==
"@tauri-apps/cli-win32-x64-msvc@1.4.0": "@tauri-apps/cli-win32-x64-msvc@1.5.8":
version "1.4.0" version "1.5.8"
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-1.4.0.tgz#23abe3f08c0df89111c29602f91c21a23577b908" resolved "https://registry.yarnpkg.com/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-1.5.8.tgz#a0c363969cf5a21c95c235e5bf6a94a410130761"
integrity sha512-z8Olcnwp5aYhzqUAarFjqF+oELCjuYWnB2HAJHlfsYNfDCAORY5kct3Fklz8PSsubC3U2EugWn8n42DwnThurg== integrity sha512-XjBg8VMswmD9JAHKlb10NRPfBVAZoiOJBbPRte+GP1BUQtqDnbIYcOLSnUCmNZoy3fUBJuKJUBT9tDCbkMr5fQ==
"@tauri-apps/cli@^1.4.0": "@tauri-apps/cli@^1.5.8":
version "1.4.0" version "1.5.8"
resolved "https://registry.yarnpkg.com/@tauri-apps/cli/-/cli-1.4.0.tgz#72732ae61e6b7d097e44a8a2ef5f211b2d01d98b" resolved "https://registry.yarnpkg.com/@tauri-apps/cli/-/cli-1.5.8.tgz#feaf055af370cb192b24ea4c51edf0e577269fb2"
integrity sha512-VXYr2i2iVFl98etQSQsqLzXgX96bnWiNZd1YADgatqwy/qecbd6Kl5ZAPB5R4ynsgE8A1gU7Fbzh7dCEQYFfmA== integrity sha512-c/mzk5vjjfxtH5uNXSc9h1eiprsolnoBcUwAa4/SZ3gxJ176CwrUKODz3cZBOnzs8omwagwgSN/j7K8NrdFL9g==
optionalDependencies: optionalDependencies:
"@tauri-apps/cli-darwin-arm64" "1.4.0" "@tauri-apps/cli-darwin-arm64" "1.5.8"
"@tauri-apps/cli-darwin-x64" "1.4.0" "@tauri-apps/cli-darwin-x64" "1.5.8"
"@tauri-apps/cli-linux-arm-gnueabihf" "1.4.0" "@tauri-apps/cli-linux-arm-gnueabihf" "1.5.8"
"@tauri-apps/cli-linux-arm64-gnu" "1.4.0" "@tauri-apps/cli-linux-arm64-gnu" "1.5.8"
"@tauri-apps/cli-linux-arm64-musl" "1.4.0" "@tauri-apps/cli-linux-arm64-musl" "1.5.8"
"@tauri-apps/cli-linux-x64-gnu" "1.4.0" "@tauri-apps/cli-linux-x64-gnu" "1.5.8"
"@tauri-apps/cli-linux-x64-musl" "1.4.0" "@tauri-apps/cli-linux-x64-musl" "1.5.8"
"@tauri-apps/cli-win32-arm64-msvc" "1.4.0" "@tauri-apps/cli-win32-arm64-msvc" "1.5.8"
"@tauri-apps/cli-win32-ia32-msvc" "1.4.0" "@tauri-apps/cli-win32-ia32-msvc" "1.5.8"
"@tauri-apps/cli-win32-x64-msvc" "1.4.0" "@tauri-apps/cli-win32-x64-msvc" "1.5.8"
"@trysound/sax@0.2.0": "@trysound/sax@0.2.0":
version "0.2.0" version "0.2.0"
@ -3228,7 +3315,7 @@ chalk@5.2.0:
resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.2.0.tgz#249623b7d66869c673699fb66d65723e54dfcfb3" resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.2.0.tgz#249623b7d66869c673699fb66d65723e54dfcfb3"
integrity sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA== integrity sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==
chalk@^2.0.0: chalk@^2.0.0, chalk@^2.4.2:
version "2.4.2" version "2.4.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
@ -4651,10 +4738,10 @@ functions-have-names@^1.2.2:
resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834"
integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==
fuse.js@^6.6.2: fuse.js@^7.0.0:
version "6.6.2" version "7.0.0"
resolved "https://registry.yarnpkg.com/fuse.js/-/fuse.js-6.6.2.tgz#fe463fed4b98c0226ac3da2856a415576dc9a111" resolved "https://registry.yarnpkg.com/fuse.js/-/fuse.js-7.0.0.tgz#6573c9fcd4c8268e403b4fc7d7131ffcf99a9eb2"
integrity sha512-cJaJkxCCxC8qIIcPBF9yGxY0W/tVZS3uEISDxhYIdtk8OL93pe+6Zj7LjCqVV4dzbqcriOZ+kQ/NE4RXZHsIGA== integrity sha512-14F4hBIxqKvD4Zz/XjDc3y94mNZN6pRv3U13Udo0lNLCWRBUsrMv2xwcF/y/Z5sV6+FQW+/ow68cHpm4sunt8Q==
gensync@^1.0.0-beta.2: gensync@^1.0.0-beta.2:
version "1.0.0-beta.2" version "1.0.0-beta.2"
@ -6698,10 +6785,10 @@ react-markdown@^8.0.7:
unist-util-visit "^4.0.0" unist-util-visit "^4.0.0"
vfile "^5.0.0" vfile "^5.0.0"
react-redux@^8.1.1: react-redux@^8.1.3:
version "8.1.1" version "8.1.3"
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-8.1.1.tgz#8e740f3fd864a4cd0de5ba9cdc8ad39cc9e7c81a" resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-8.1.3.tgz#4fdc0462d0acb59af29a13c27ffef6f49ab4df46"
integrity sha512-5W0QaKtEhj+3bC0Nj0NkqkhIv8gLADH/2kYFMTHxCVqQILiWzLv6MaLuV5wJU3BQEdHKzTfcvPN0WMS6SC1oyA== integrity sha512-n0ZrutD7DaX/j9VscF+uTALI3oUPa/pO4Z3soOBIjuRn/FzVu6aehhysxZCLi6y7duMf52WNZGMl7CtuK5EnRw==
dependencies: dependencies:
"@babel/runtime" "^7.12.1" "@babel/runtime" "^7.12.1"
"@types/hoist-non-react-statics" "^3.3.1" "@types/hoist-non-react-statics" "^3.3.1"
@ -6758,10 +6845,10 @@ regenerate@^1.4.2:
resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a" resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a"
integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==
regenerator-runtime@^0.13.11: regenerator-runtime@^0.14.0:
version "0.13.11" version "0.14.1"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f"
integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==
regenerator-transform@^0.15.1: regenerator-transform@^0.15.1:
version "0.15.1" version "0.15.1"