mirror of
https://github.com/Yidadaa/ChatGPT-Next-Web.git
synced 2025-08-31 03:09:04 +08:00
Compare commits
49 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
8f21e736dd | ||
|
222301307f | ||
|
1b19fdfe11 | ||
|
1f2ef1cdb7 | ||
|
c394b21423 | ||
|
696e84ea88 | ||
|
4a17dca7b9 | ||
|
9bcfe6461d | ||
|
4a82a91f2d | ||
|
24cd905911 | ||
|
6d62ab4257 | ||
|
f6ff32f339 | ||
|
bc523d302b | ||
|
1facbb2906 | ||
|
8265436437 | ||
|
96545bd523 | ||
|
e773ed2912 | ||
|
9949fc4646 | ||
|
d88da1f6ab | ||
|
fe8e3f2bcf | ||
|
4b9d753254 | ||
|
5ba385b74d | ||
|
ec655f5182 | ||
|
093df395c7 | ||
|
92d2373d77 | ||
|
319959ad6e | ||
|
de1e4b4c6c | ||
|
c2e79d22d2 | ||
|
596c9b1d27 | ||
|
40223e6b3f | ||
|
eec1dd6448 | ||
|
6c1261d28d | ||
|
4697ea1c1a | ||
|
6e20031dce | ||
|
a5fe9bc6d6 | ||
|
637660df2a | ||
|
e1549d109e | ||
|
5f8fc3d155 | ||
|
fce3b3ce7b | ||
|
074bd9f045 | ||
|
b1ea26467d | ||
|
48ebd74859 | ||
|
ef5b7ce853 | ||
|
e0053d57f7 | ||
|
06268543d0 | ||
|
58eadd6d7b | ||
|
f250594e97 | ||
|
c1b6828ed4 | ||
|
328ecd1cfb |
@@ -8,7 +8,7 @@ WORKDIR /app
|
|||||||
|
|
||||||
COPY package.json yarn.lock ./
|
COPY package.json yarn.lock ./
|
||||||
|
|
||||||
RUN yarn config set registry 'https://registry.npm.taobao.org'
|
RUN yarn config set registry 'https://registry.npmmirror.com/'
|
||||||
RUN yarn install
|
RUN yarn install
|
||||||
|
|
||||||
FROM base AS builder
|
FROM base AS builder
|
||||||
|
24
README.md
24
README.md
@@ -9,10 +9,9 @@ One-Click to deploy well-designed ChatGPT web UI on Vercel.
|
|||||||
|
|
||||||
一键免费部署你的私人 ChatGPT 网页应用。
|
一键免费部署你的私人 ChatGPT 网页应用。
|
||||||
|
|
||||||
[Demo](https://chat-gpt-next-web.vercel.app/) / [Issues](https://github.com/Yidadaa/ChatGPT-Next-Web/issues) / [Join Discord](https://discord.gg/zrhvHCr79N) / [Buy Me a Coffee](https://www.buymeacoffee.com/yidadaa)
|
[Demo](https://chatgpt.nextweb.fun/) / [Issues](https://github.com/Yidadaa/ChatGPT-Next-Web/issues) / [Join Discord](https://discord.gg/zrhvHCr79N) / [Buy Me a Coffee](https://www.buymeacoffee.com/yidadaa)
|
||||||
|
|
||||||
[演示](https://chat-gpt-next-web.vercel.app/) / [反馈](https://github.com/Yidadaa/ChatGPT-Next-Web/issues) / [QQ 群](https://user-images.githubusercontent.com/16968934/234462588-e8eff256-f5ca-46ef-8f5f-d7db6d28735a.jpg) / [打赏开发者](https://user-images.githubusercontent.com/16968934/227772541-5bcd52d8-61b7-488c-a203-0330d8006e2b.jpg)
|
|
||||||
|
|
||||||
|
[演示](https://chatgpt.nextweb.fun/) / [反馈](https://github.com/Yidadaa/ChatGPT-Next-Web/issues) / [QQ 群](https://user-images.githubusercontent.com/16968934/236402186-fa76e930-64f5-47ae-b967-b0f04b1fbf56.jpg) / [打赏开发者](https://user-images.githubusercontent.com/16968934/227772541-5bcd52d8-61b7-488c-a203-0330d8006e2b.jpg)
|
||||||
|
|
||||||
[](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FYidadaa%2FChatGPT-Next-Web&env=OPENAI_API_KEY&env=CODE&project-name=chatgpt-next-web&repository-name=ChatGPT-Next-Web)
|
[](https://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)
|
||||||
|
|
||||||
@@ -26,13 +25,13 @@ One-Click to deploy well-designed ChatGPT web UI on Vercel.
|
|||||||
|
|
||||||
- **Deploy for free with one-click** on Vercel in under 1 minute
|
- **Deploy for free with one-click** on Vercel in under 1 minute
|
||||||
- Privacy first, all data stored locally in the browser
|
- Privacy first, all data stored locally in the browser
|
||||||
|
- Markdown support: LaTex, mermaid, code highlight, etc.
|
||||||
- Responsive design, dark mode and PWA
|
- Responsive design, dark mode and PWA
|
||||||
- Fast first screen loading speed (~100kb), support streaming response
|
- Fast first screen loading speed (~100kb), support streaming response
|
||||||
- New in v2: create, share and debug your chat tools with prompt templates (mask)
|
- New in v2: create, share and debug your chat tools with prompt templates (mask)
|
||||||
- Awesome prompts powered by [awesome-chatgpt-prompts-zh](https://github.com/PlexPt/awesome-chatgpt-prompts-zh) and [awesome-chatgpt-prompts](https://github.com/f/awesome-chatgpt-prompts)
|
- Awesome prompts powered by [awesome-chatgpt-prompts-zh](https://github.com/PlexPt/awesome-chatgpt-prompts-zh) and [awesome-chatgpt-prompts](https://github.com/f/awesome-chatgpt-prompts)
|
||||||
- Automatically compresses chat history to support long conversations while also saving your tokens
|
- Automatically compresses chat history to support long conversations while also saving your tokens
|
||||||
- One-click export all chat history with full Markdown support
|
- I18n: English, 简体中文, 繁体中文, 日本語, Español, Italiano, Türkçe, Deutsch
|
||||||
- I18n supported
|
|
||||||
|
|
||||||
## Roadmap
|
## Roadmap
|
||||||
|
|
||||||
@@ -50,18 +49,20 @@ One-Click to deploy well-designed ChatGPT web UI on Vercel.
|
|||||||
- UI text customize
|
- UI text customize
|
||||||
|
|
||||||
## What's New
|
## What's New
|
||||||
|
|
||||||
- 🚀 v2.0 is released, now you can create prompt templates, turn your ideas into reality! Read this: [ChatGPT Prompt Engineering Tips: Zero, One and Few Shot Prompting](https://www.allabtai.com/prompt-engineering-tips-zero-one-and-few-shot-prompting/).
|
- 🚀 v2.0 is released, now you can create prompt templates, turn your ideas into reality! Read this: [ChatGPT Prompt Engineering Tips: Zero, One and Few Shot Prompting](https://www.allabtai.com/prompt-engineering-tips-zero-one-and-few-shot-prompting/).
|
||||||
|
|
||||||
## 主要功能
|
## 主要功能
|
||||||
|
|
||||||
- 在 1 分钟内使用 Vercel **免费一键部署**
|
- 在 1 分钟内使用 Vercel **免费一键部署**
|
||||||
|
- 完整的 Markdown 支持:LaTex 公式、Mermaid 流程图、代码高亮等等
|
||||||
- 精心设计的 UI,响应式设计,支持深色模式,支持 PWA
|
- 精心设计的 UI,响应式设计,支持深色模式,支持 PWA
|
||||||
- 极快的首屏加载速度(~100kb),支持流式响应
|
- 极快的首屏加载速度(~100kb),支持流式响应
|
||||||
- 隐私安全,所有数据保存在用户浏览器本地
|
- 隐私安全,所有数据保存在用户浏览器本地
|
||||||
- 预制角色功能(面具),方便地创建、分享和调试你的个性化对话
|
- 预制角色功能(面具),方便地创建、分享和调试你的个性化对话
|
||||||
- 海量的内置 prompt 列表,来自[中文](https://github.com/PlexPt/awesome-chatgpt-prompts-zh)和[英文](https://github.com/f/awesome-chatgpt-prompts)
|
- 海量的内置 prompt 列表,来自[中文](https://github.com/PlexPt/awesome-chatgpt-prompts-zh)和[英文](https://github.com/f/awesome-chatgpt-prompts)
|
||||||
- 自动压缩上下文聊天记录,在节省 Token 的同时支持超长对话
|
- 自动压缩上下文聊天记录,在节省 Token 的同时支持超长对话
|
||||||
- 一键导出聊天记录,完整的 Markdown 支持
|
- 多国语言支持:English, 简体中文, 繁体中文, 日本語, Español, Italiano, Türkçe, Deutsch
|
||||||
- 拥有自己的域名?好上加好,绑定后即可在任何地方**无障碍**快速访问
|
- 拥有自己的域名?好上加好,绑定后即可在任何地方**无障碍**快速访问
|
||||||
|
|
||||||
## 开发计划
|
## 开发计划
|
||||||
@@ -80,10 +81,9 @@ One-Click to deploy well-designed ChatGPT web UI on Vercel.
|
|||||||
- 用户登录、账号管理、消息云同步
|
- 用户登录、账号管理、消息云同步
|
||||||
|
|
||||||
## 最新动态
|
## 最新动态
|
||||||
|
|
||||||
- 🚀 v2.0 已经发布,现在你可以使用面具功能快速创建预制对话了! 了解更多: [ChatGPT 提示词高阶技能:零次、一次和少样本提示](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/138)。
|
- 🚀 v2.0 已经发布,现在你可以使用面具功能快速创建预制对话了! 了解更多: [ChatGPT 提示词高阶技能:零次、一次和少样本提示](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/138)。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Get Started
|
## Get Started
|
||||||
|
|
||||||
> [简体中文 > 如何开始使用](./README_CN.md#开始使用)
|
> [简体中文 > 如何开始使用](./README_CN.md#开始使用)
|
||||||
@@ -163,6 +163,12 @@ Override openai api request base url.
|
|||||||
|
|
||||||
Specify OpenAI organization ID.
|
Specify OpenAI organization ID.
|
||||||
|
|
||||||
|
### `HIDE_USER_API_KEY` (optional)
|
||||||
|
|
||||||
|
> Default: Empty
|
||||||
|
|
||||||
|
If you do not want users to input their own API key, set this environment variable to 1.
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
> [简体中文 > 如何进行二次开发](./README_CN.md#开发)
|
> [简体中文 > 如何进行二次开发](./README_CN.md#开发)
|
||||||
@@ -248,7 +254,7 @@ bash <(curl -s https://raw.githubusercontent.com/Yidadaa/ChatGPT-Next-Web/main/s
|
|||||||
[@hotic](https://github.com/hotic)
|
[@hotic](https://github.com/hotic)
|
||||||
[@WingCH](https://github.com/WingCH)
|
[@WingCH](https://github.com/WingCH)
|
||||||
[@jtung4](https://github.com/jtung4)
|
[@jtung4](https://github.com/jtung4)
|
||||||
|
[@micozhu](https://github.com/micozhu)
|
||||||
|
|
||||||
### Contributor
|
### Contributor
|
||||||
|
|
||||||
|
12
README_CN.md
12
README_CN.md
@@ -33,6 +33,7 @@
|
|||||||
- 在 Vercel 重新选择并部署,[请查看详细教程](./docs/vercel-cn.md#如何新建项目)。
|
- 在 Vercel 重新选择并部署,[请查看详细教程](./docs/vercel-cn.md#如何新建项目)。
|
||||||
|
|
||||||
### 打开自动更新
|
### 打开自动更新
|
||||||
|
|
||||||
> 如果你遇到了 Upstream Sync 执行错误,请手动 Sync Fork 一次!
|
> 如果你遇到了 Upstream Sync 执行错误,请手动 Sync Fork 一次!
|
||||||
|
|
||||||
当你 fork 项目之后,由于 Github 的限制,需要手动去你 fork 后的项目的 Actions 页面启用 Workflows,并启用 Upstream Sync Action,启用之后即可开启每小时定时自动更新:
|
当你 fork 项目之后,由于 Github 的限制,需要手动去你 fork 后的项目的 Actions 页面启用 Workflows,并启用 Upstream Sync Action,启用之后即可开启每小时定时自动更新:
|
||||||
@@ -89,6 +90,10 @@ OpenAI 接口代理 URL,如果你手动配置了 openai 接口代理,请填
|
|||||||
|
|
||||||
指定 OpenAI 中的组织 ID。
|
指定 OpenAI 中的组织 ID。
|
||||||
|
|
||||||
|
### `HIDE_USER_API_KEY` (可选)
|
||||||
|
|
||||||
|
如果你不想让用户自行填入 API Key,将此环境变量设置为 1 即可。
|
||||||
|
|
||||||
## 开发
|
## 开发
|
||||||
|
|
||||||
> 强烈不建议在本地进行开发或者部署,由于一些技术原因,很难在本地配置好 OpenAI API 代理,除非你能保证可以直连 OpenAI 服务器。
|
> 强烈不建议在本地进行开发或者部署,由于一些技术原因,很难在本地配置好 OpenAI API 代理,除非你能保证可以直连 OpenAI 服务器。
|
||||||
@@ -106,15 +111,16 @@ OPENAI_API_KEY=<your api key here>
|
|||||||
### 本地开发
|
### 本地开发
|
||||||
|
|
||||||
1. 安装 nodejs 18 和 yarn,具体细节请询问 ChatGPT;
|
1. 安装 nodejs 18 和 yarn,具体细节请询问 ChatGPT;
|
||||||
2. 执行 `yarn install && yarn dev` 即可。⚠️注意:此命令仅用于本地开发,不要用于部署!
|
2. 执行 `yarn install && yarn dev` 即可。⚠️ 注意:此命令仅用于本地开发,不要用于部署!
|
||||||
3. 如果你想本地部署,请使用 `yarn install && yarn start` 命令,你可以配合 pm2 来守护进程,防止被杀死,详情询问 ChatGPT。
|
3. 如果你想本地部署,请使用 `yarn install && yarn start` 命令,你可以配合 pm2 来守护进程,防止被杀死,详情询问 ChatGPT。
|
||||||
|
|
||||||
## 部署
|
## 部署
|
||||||
|
|
||||||
### 容器部署 (推荐)
|
### 容器部署 (推荐)
|
||||||
|
|
||||||
> Docker 版本需要在 20 及其以上,否则会提示找不到镜像。
|
> Docker 版本需要在 20 及其以上,否则会提示找不到镜像。
|
||||||
|
|
||||||
> ⚠️注意:docker 版本在大多数时间都会落后最新的版本 1 到 2 天,所以部署后会持续出现“存在更新”的提示,属于正常现象。
|
> ⚠️ 注意:docker 版本在大多数时间都会落后最新的版本 1 到 2 天,所以部署后会持续出现“存在更新”的提示,属于正常现象。
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
docker pull yidadaa/chatgpt-next-web
|
docker pull yidadaa/chatgpt-next-web
|
||||||
@@ -146,7 +152,7 @@ docker run -d -p 3000:3000 \
|
|||||||
bash <(curl -s https://raw.githubusercontent.com/Yidadaa/ChatGPT-Next-Web/main/scripts/setup.sh)
|
bash <(curl -s https://raw.githubusercontent.com/Yidadaa/ChatGPT-Next-Web/main/scripts/setup.sh)
|
||||||
```
|
```
|
||||||
|
|
||||||
⚠️注意:如果你安装过程中遇到了问题,请使用 docker 部署。
|
⚠️ 注意:如果你安装过程中遇到了问题,请使用 docker 部署。
|
||||||
|
|
||||||
## 鸣谢
|
## 鸣谢
|
||||||
|
|
||||||
|
71
app/api/auth.ts
Normal file
71
app/api/auth.ts
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
import { NextRequest } from "next/server";
|
||||||
|
import { getServerSideConfig } from "../config/server";
|
||||||
|
import md5 from "spark-md5";
|
||||||
|
import { ACCESS_CODE_PREFIX } from "../constant";
|
||||||
|
|
||||||
|
const serverConfig = getServerSideConfig();
|
||||||
|
|
||||||
|
function getIP(req: NextRequest) {
|
||||||
|
let ip = req.ip ?? req.headers.get("x-real-ip");
|
||||||
|
const forwardedFor = req.headers.get("x-forwarded-for");
|
||||||
|
|
||||||
|
if (!ip && forwardedFor) {
|
||||||
|
ip = forwardedFor.split(",").at(0) ?? "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return ip;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseApiKey(bearToken: string) {
|
||||||
|
const token = bearToken.trim().replaceAll("Bearer ", "").trim();
|
||||||
|
const isOpenAiKey = !token.startsWith(ACCESS_CODE_PREFIX);
|
||||||
|
|
||||||
|
return {
|
||||||
|
accessCode: isOpenAiKey ? "" : token.slice(ACCESS_CODE_PREFIX.length),
|
||||||
|
apiKey: isOpenAiKey ? token : "",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function auth(req: NextRequest) {
|
||||||
|
const authToken = req.headers.get("Authorization") ?? "";
|
||||||
|
|
||||||
|
// check if it is openai api key or user token
|
||||||
|
const { accessCode, apiKey: token } = parseApiKey(authToken);
|
||||||
|
|
||||||
|
const hashedCode = md5.hash(accessCode ?? "").trim();
|
||||||
|
|
||||||
|
console.log("[Auth] allowed hashed codes: ", [...serverConfig.codes]);
|
||||||
|
console.log("[Auth] got access code:", accessCode);
|
||||||
|
console.log("[Auth] hashed access code:", hashedCode);
|
||||||
|
console.log("[User IP] ", getIP(req));
|
||||||
|
console.log("[Time] ", new Date().toLocaleString());
|
||||||
|
|
||||||
|
if (serverConfig.needCode && !serverConfig.codes.has(hashedCode) && !token) {
|
||||||
|
return {
|
||||||
|
error: true,
|
||||||
|
needAccessCode: true,
|
||||||
|
msg: "Please go settings page and fill your access code.",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// if user does not provide an api key, inject system api key
|
||||||
|
if (!token) {
|
||||||
|
const apiKey = serverConfig.apiKey;
|
||||||
|
if (apiKey) {
|
||||||
|
console.log("[Auth] use system api key");
|
||||||
|
req.headers.set("Authorization", `Bearer ${apiKey}`);
|
||||||
|
} else {
|
||||||
|
console.log("[Auth] admin did not provide an api key");
|
||||||
|
return {
|
||||||
|
error: true,
|
||||||
|
msg: "Empty Api Key",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log("[Auth] use user api key");
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
error: false,
|
||||||
|
};
|
||||||
|
}
|
@@ -1,62 +0,0 @@
|
|||||||
import { createParser } from "eventsource-parser";
|
|
||||||
import { NextRequest } from "next/server";
|
|
||||||
import { requestOpenai } from "../common";
|
|
||||||
|
|
||||||
async function createStream(req: NextRequest) {
|
|
||||||
const encoder = new TextEncoder();
|
|
||||||
const decoder = new TextDecoder();
|
|
||||||
|
|
||||||
const res = await requestOpenai(req);
|
|
||||||
|
|
||||||
const contentType = res.headers.get("Content-Type") ?? "";
|
|
||||||
if (!contentType.includes("stream")) {
|
|
||||||
const content = await (
|
|
||||||
await res.text()
|
|
||||||
).replace(/provided:.*. You/, "provided: ***. You");
|
|
||||||
console.log("[Stream] error ", content);
|
|
||||||
return "```json\n" + content + "```";
|
|
||||||
}
|
|
||||||
|
|
||||||
const stream = new ReadableStream({
|
|
||||||
async start(controller) {
|
|
||||||
function onParse(event: any) {
|
|
||||||
if (event.type === "event") {
|
|
||||||
const data = event.data;
|
|
||||||
// https://beta.openai.com/docs/api-reference/completions/create#completions/create-stream
|
|
||||||
if (data === "[DONE]") {
|
|
||||||
controller.close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const json = JSON.parse(data);
|
|
||||||
const text = json.choices[0].delta.content;
|
|
||||||
const queue = encoder.encode(text);
|
|
||||||
controller.enqueue(queue);
|
|
||||||
} catch (e) {
|
|
||||||
controller.error(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const parser = createParser(onParse);
|
|
||||||
for await (const chunk of res.body as any) {
|
|
||||||
parser.feed(decoder.decode(chunk, { stream: true }));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return stream;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function POST(req: NextRequest) {
|
|
||||||
try {
|
|
||||||
const stream = await createStream(req);
|
|
||||||
return new Response(stream);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("[Chat Stream]", error);
|
|
||||||
return new Response(
|
|
||||||
["```json\n", JSON.stringify(error, null, " "), "\n```"].join(""),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const runtime = "edge";
|
|
@@ -6,8 +6,11 @@ const PROTOCOL = process.env.PROTOCOL ?? DEFAULT_PROTOCOL;
|
|||||||
const BASE_URL = process.env.BASE_URL ?? OPENAI_URL;
|
const BASE_URL = process.env.BASE_URL ?? OPENAI_URL;
|
||||||
|
|
||||||
export async function requestOpenai(req: NextRequest) {
|
export async function requestOpenai(req: NextRequest) {
|
||||||
const apiKey = req.headers.get("token");
|
const authValue = req.headers.get("Authorization") ?? "";
|
||||||
const openaiPath = req.headers.get("path");
|
const openaiPath = `${req.nextUrl.pathname}${req.nextUrl.search}`.replaceAll(
|
||||||
|
"/api/openai/",
|
||||||
|
"",
|
||||||
|
);
|
||||||
|
|
||||||
let baseUrl = BASE_URL;
|
let baseUrl = BASE_URL;
|
||||||
|
|
||||||
@@ -22,10 +25,14 @@ export async function requestOpenai(req: NextRequest) {
|
|||||||
console.log("[Org ID]", process.env.OPENAI_ORG_ID);
|
console.log("[Org ID]", process.env.OPENAI_ORG_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!authValue || !authValue.startsWith("Bearer sk-")) {
|
||||||
|
console.error("[OpenAI Request] invalid api key provided", authValue);
|
||||||
|
}
|
||||||
|
|
||||||
return fetch(`${baseUrl}/${openaiPath}`, {
|
return fetch(`${baseUrl}/${openaiPath}`, {
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
Authorization: `Bearer ${apiKey}`,
|
Authorization: authValue,
|
||||||
...(process.env.OPENAI_ORG_ID && {
|
...(process.env.OPENAI_ORG_ID && {
|
||||||
"OpenAI-Organization": process.env.OPENAI_ORG_ID,
|
"OpenAI-Organization": process.env.OPENAI_ORG_ID,
|
||||||
}),
|
}),
|
||||||
|
@@ -8,16 +8,18 @@ const serverConfig = getServerSideConfig();
|
|||||||
// 警告!不要在这里写入任何敏感信息!
|
// 警告!不要在这里写入任何敏感信息!
|
||||||
const DANGER_CONFIG = {
|
const DANGER_CONFIG = {
|
||||||
needCode: serverConfig.needCode,
|
needCode: serverConfig.needCode,
|
||||||
|
hideUserApiKey: serverConfig.hideUserApiKey,
|
||||||
};
|
};
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
type DangerConfig = typeof DANGER_CONFIG;
|
type DangerConfig = typeof DANGER_CONFIG;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function POST(req: NextRequest) {
|
async function handle() {
|
||||||
return NextResponse.json({
|
return NextResponse.json(DANGER_CONFIG);
|
||||||
needCode: serverConfig.needCode,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const GET = handle;
|
||||||
|
export const POST = handle;
|
||||||
|
|
||||||
export const runtime = "edge";
|
export const runtime = "edge";
|
||||||
|
101
app/api/openai/[...path]/route.ts
Normal file
101
app/api/openai/[...path]/route.ts
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
import { createParser } from "eventsource-parser";
|
||||||
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
import { auth } from "../../auth";
|
||||||
|
import { requestOpenai } from "../../common";
|
||||||
|
|
||||||
|
async function createStream(res: Response) {
|
||||||
|
const encoder = new TextEncoder();
|
||||||
|
const decoder = new TextDecoder();
|
||||||
|
|
||||||
|
const stream = new ReadableStream({
|
||||||
|
async start(controller) {
|
||||||
|
function onParse(event: any) {
|
||||||
|
if (event.type === "event") {
|
||||||
|
const data = event.data;
|
||||||
|
// https://beta.openai.com/docs/api-reference/completions/create#completions/create-stream
|
||||||
|
if (data === "[DONE]") {
|
||||||
|
controller.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const json = JSON.parse(data);
|
||||||
|
const text = json.choices[0].delta.content;
|
||||||
|
const queue = encoder.encode(text);
|
||||||
|
controller.enqueue(queue);
|
||||||
|
} catch (e) {
|
||||||
|
controller.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const parser = createParser(onParse);
|
||||||
|
for await (const chunk of res.body as any) {
|
||||||
|
parser.feed(decoder.decode(chunk, { stream: true }));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatResponse(msg: any) {
|
||||||
|
const jsonMsg = ["```json\n", JSON.stringify(msg, null, " "), "\n```"].join(
|
||||||
|
"",
|
||||||
|
);
|
||||||
|
return new Response(jsonMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handle(
|
||||||
|
req: NextRequest,
|
||||||
|
{ params }: { params: { path: string[] } },
|
||||||
|
) {
|
||||||
|
console.log("[OpenAI Route] params ", params);
|
||||||
|
|
||||||
|
const authResult = auth(req);
|
||||||
|
if (authResult.error) {
|
||||||
|
return NextResponse.json(authResult, {
|
||||||
|
status: 401,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const api = await requestOpenai(req);
|
||||||
|
|
||||||
|
const contentType = api.headers.get("Content-Type") ?? "";
|
||||||
|
|
||||||
|
// streaming response
|
||||||
|
if (contentType.includes("stream")) {
|
||||||
|
const stream = await createStream(api);
|
||||||
|
const res = new Response(stream);
|
||||||
|
res.headers.set("Content-Type", contentType);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
// try to parse error msg
|
||||||
|
try {
|
||||||
|
const mayBeErrorBody = await api.json();
|
||||||
|
if (mayBeErrorBody.error) {
|
||||||
|
console.error("[OpenAI Response] ", mayBeErrorBody);
|
||||||
|
return formatResponse(mayBeErrorBody);
|
||||||
|
} else {
|
||||||
|
const res = new Response(JSON.stringify(mayBeErrorBody));
|
||||||
|
res.headers.set("Content-Type", "application/json");
|
||||||
|
res.headers.set("Cache-Control", "no-cache");
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("[OpenAI Parse] ", e);
|
||||||
|
return formatResponse({
|
||||||
|
msg: "invalid response from openai server",
|
||||||
|
error: e,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("[OpenAI] ", e);
|
||||||
|
return formatResponse(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const GET = handle;
|
||||||
|
export const POST = handle;
|
||||||
|
|
||||||
|
export const runtime = "edge";
|
@@ -1,33 +0,0 @@
|
|||||||
import { NextRequest, NextResponse } from "next/server";
|
|
||||||
import { requestOpenai } from "../common";
|
|
||||||
|
|
||||||
async function makeRequest(req: NextRequest) {
|
|
||||||
try {
|
|
||||||
const api = await requestOpenai(req);
|
|
||||||
const res = new NextResponse(api.body);
|
|
||||||
res.headers.set("Content-Type", "application/json");
|
|
||||||
res.headers.set("Cache-Control", "no-cache");
|
|
||||||
return res;
|
|
||||||
} catch (e) {
|
|
||||||
console.error("[OpenAI] ", req.body, e);
|
|
||||||
return NextResponse.json(
|
|
||||||
{
|
|
||||||
error: true,
|
|
||||||
msg: JSON.stringify(e),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
status: 500,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function POST(req: NextRequest) {
|
|
||||||
return makeRequest(req);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function GET(req: NextRequest) {
|
|
||||||
return makeRequest(req);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const runtime = "edge";
|
|
28
app/command.ts
Normal file
28
app/command.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { useSearchParams } from "react-router-dom";
|
||||||
|
|
||||||
|
type Command = (param: string) => void;
|
||||||
|
interface Commands {
|
||||||
|
fill?: Command;
|
||||||
|
submit?: Command;
|
||||||
|
mask?: Command;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useCommand(commands: Commands = {}) {
|
||||||
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
|
|
||||||
|
if (commands === undefined) return;
|
||||||
|
|
||||||
|
let shouldUpdate = false;
|
||||||
|
searchParams.forEach((param, name) => {
|
||||||
|
const commandName = name as keyof Commands;
|
||||||
|
if (typeof commands[commandName] === "function") {
|
||||||
|
commands[commandName]!(param);
|
||||||
|
searchParams.delete(name);
|
||||||
|
shouldUpdate = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (shouldUpdate) {
|
||||||
|
setSearchParams(searchParams);
|
||||||
|
}
|
||||||
|
}
|
@@ -26,12 +26,10 @@ import {
|
|||||||
SubmitKey,
|
SubmitKey,
|
||||||
useChatStore,
|
useChatStore,
|
||||||
BOT_HELLO,
|
BOT_HELLO,
|
||||||
ROLES,
|
|
||||||
createMessage,
|
createMessage,
|
||||||
useAccessStore,
|
useAccessStore,
|
||||||
Theme,
|
Theme,
|
||||||
useAppConfig,
|
useAppConfig,
|
||||||
ModelConfig,
|
|
||||||
DEFAULT_TOPIC,
|
DEFAULT_TOPIC,
|
||||||
} from "../store";
|
} from "../store";
|
||||||
|
|
||||||
@@ -54,15 +52,12 @@ import styles from "./home.module.scss";
|
|||||||
import chatStyle from "./chat.module.scss";
|
import chatStyle from "./chat.module.scss";
|
||||||
|
|
||||||
import { ListItem, Modal, showModal } from "./ui-lib";
|
import { ListItem, Modal, showModal } from "./ui-lib";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useLocation, useNavigate } from "react-router-dom";
|
||||||
import { Path } from "../constant";
|
import { Path } from "../constant";
|
||||||
import { Avatar } from "./emoji";
|
import { Avatar } from "./emoji";
|
||||||
import { MaskAvatar, MaskConfig } from "./mask";
|
import { MaskAvatar, MaskConfig } from "./mask";
|
||||||
import {
|
import { useMaskStore } from "../store/mask";
|
||||||
DEFAULT_MASK_AVATAR,
|
import { useCommand } from "../command";
|
||||||
DEFAULT_MASK_ID,
|
|
||||||
useMaskStore,
|
|
||||||
} from "../store/mask";
|
|
||||||
|
|
||||||
const Markdown = dynamic(async () => (await import("./markdown")).Markdown, {
|
const Markdown = dynamic(async () => (await import("./markdown")).Markdown, {
|
||||||
loading: () => <LoadingIcon />,
|
loading: () => <LoadingIcon />,
|
||||||
@@ -224,15 +219,63 @@ export function PromptHints(props: {
|
|||||||
prompts: Prompt[];
|
prompts: Prompt[];
|
||||||
onPromptSelect: (prompt: Prompt) => void;
|
onPromptSelect: (prompt: Prompt) => void;
|
||||||
}) {
|
}) {
|
||||||
if (props.prompts.length === 0) return null;
|
const noPrompts = props.prompts.length === 0;
|
||||||
|
const [selectIndex, setSelectIndex] = useState(0);
|
||||||
|
const selectedRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSelectIndex(0);
|
||||||
|
}, [props.prompts.length]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const onKeyDown = (e: KeyboardEvent) => {
|
||||||
|
if (noPrompts) return;
|
||||||
|
|
||||||
|
// arrow up / down to select prompt
|
||||||
|
const changeIndex = (delta: number) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
const nextIndex = Math.max(
|
||||||
|
0,
|
||||||
|
Math.min(props.prompts.length - 1, selectIndex + delta),
|
||||||
|
);
|
||||||
|
setSelectIndex(nextIndex);
|
||||||
|
selectedRef.current?.scrollIntoView({
|
||||||
|
block: "center",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
if (e.key === "ArrowUp") {
|
||||||
|
changeIndex(1);
|
||||||
|
} else if (e.key === "ArrowDown") {
|
||||||
|
changeIndex(-1);
|
||||||
|
} else if (e.key === "Enter") {
|
||||||
|
const selectedPrompt = props.prompts.at(selectIndex);
|
||||||
|
if (selectedPrompt) {
|
||||||
|
props.onPromptSelect(selectedPrompt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener("keydown", onKeyDown);
|
||||||
|
|
||||||
|
return () => window.removeEventListener("keydown", onKeyDown);
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [noPrompts, selectIndex]);
|
||||||
|
|
||||||
|
if (noPrompts) return null;
|
||||||
return (
|
return (
|
||||||
<div className={styles["prompt-hints"]}>
|
<div className={styles["prompt-hints"]}>
|
||||||
{props.prompts.map((prompt, i) => (
|
{props.prompts.map((prompt, i) => (
|
||||||
<div
|
<div
|
||||||
className={styles["prompt-hint"]}
|
ref={i === selectIndex ? selectedRef : null}
|
||||||
|
className={
|
||||||
|
styles["prompt-hint"] +
|
||||||
|
` ${i === selectIndex ? styles["prompt-hint-selected"] : ""}`
|
||||||
|
}
|
||||||
key={prompt.title + i.toString()}
|
key={prompt.title + i.toString()}
|
||||||
onClick={() => props.onPromptSelect(prompt)}
|
onClick={() => props.onPromptSelect(prompt)}
|
||||||
|
onMouseEnter={() => setSelectIndex(i)}
|
||||||
>
|
>
|
||||||
<div className={styles["hint-title"]}>{prompt.title}</div>
|
<div className={styles["hint-title"]}>{prompt.title}</div>
|
||||||
<div className={styles["hint-content"]}>{prompt.content}</div>
|
<div className={styles["hint-content"]}>{prompt.content}</div>
|
||||||
@@ -370,7 +413,7 @@ export function Chat() {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const onChatBodyScroll = (e: HTMLElement) => {
|
const onChatBodyScroll = (e: HTMLElement) => {
|
||||||
const isTouchBottom = e.scrollTop + e.clientHeight >= e.scrollHeight - 20;
|
const isTouchBottom = e.scrollTop + e.clientHeight >= e.scrollHeight - 100;
|
||||||
setHitBottom(isTouchBottom);
|
setHitBottom(isTouchBottom);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -397,7 +440,7 @@ export function Chat() {
|
|||||||
() => {
|
() => {
|
||||||
const rows = inputRef.current ? autoGrowTextArea(inputRef.current) : 1;
|
const rows = inputRef.current ? autoGrowTextArea(inputRef.current) : 1;
|
||||||
const inputRows = Math.min(
|
const inputRows = Math.min(
|
||||||
5,
|
20,
|
||||||
Math.max(2 + Number(!isMobileScreen), rows),
|
Math.max(2 + Number(!isMobileScreen), rows),
|
||||||
);
|
);
|
||||||
setInputRows(inputRows);
|
setInputRows(inputRows);
|
||||||
@@ -430,9 +473,8 @@ export function Chat() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// submit user input
|
const doSubmit = (userInput: string) => {
|
||||||
const onUserSubmit = () => {
|
if (userInput.trim() === "") return;
|
||||||
if (userInput.length <= 0) return;
|
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
chatStore.onUserInput(userInput).then(() => setIsLoading(false));
|
chatStore.onUserInput(userInput).then(() => setIsLoading(false));
|
||||||
setBeforeInput(userInput);
|
setBeforeInput(userInput);
|
||||||
@@ -456,7 +498,7 @@ export function Chat() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (shouldSubmit(e)) {
|
if (shouldSubmit(e)) {
|
||||||
onUserSubmit();
|
doSubmit(userInput);
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -566,12 +608,16 @@ export function Chat() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Auto focus
|
const location = useLocation();
|
||||||
useEffect(() => {
|
const isChat = location.pathname === Path.Chat;
|
||||||
if (isMobileScreen) return;
|
const autoFocus = !isMobileScreen || isChat; // only focus in chat page
|
||||||
inputRef.current?.focus();
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
useCommand({
|
||||||
}, []);
|
fill: setUserInput,
|
||||||
|
submit: (text) => {
|
||||||
|
doSubmit(text);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.chat} key={session.id}>
|
<div className={styles.chat} key={session.id}>
|
||||||
@@ -762,23 +808,16 @@ export function Chat() {
|
|||||||
value={userInput}
|
value={userInput}
|
||||||
onKeyDown={onInputKeyDown}
|
onKeyDown={onInputKeyDown}
|
||||||
onFocus={() => setAutoScroll(true)}
|
onFocus={() => setAutoScroll(true)}
|
||||||
onBlur={() => {
|
onBlur={() => setAutoScroll(false)}
|
||||||
setTimeout(() => {
|
|
||||||
if (document.activeElement !== inputRef.current) {
|
|
||||||
setAutoScroll(false);
|
|
||||||
setPromptHints([]);
|
|
||||||
}
|
|
||||||
}, 100);
|
|
||||||
}}
|
|
||||||
autoFocus
|
|
||||||
rows={inputRows}
|
rows={inputRows}
|
||||||
|
autoFocus={autoFocus}
|
||||||
/>
|
/>
|
||||||
<IconButton
|
<IconButton
|
||||||
icon={<SendWhiteIcon />}
|
icon={<SendWhiteIcon />}
|
||||||
text={Locale.Chat.Send}
|
text={Locale.Chat.Send}
|
||||||
className={styles["chat-input-send"]}
|
className={styles["chat-input-send"]}
|
||||||
type="primary"
|
type="primary"
|
||||||
onClick={onUserSubmit}
|
onClick={() => doSubmit(userInput)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -335,6 +335,7 @@
|
|||||||
padding: 20px;
|
padding: 20px;
|
||||||
padding-bottom: 40px;
|
padding-bottom: 40px;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
overscroll-behavior: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-body-title {
|
.chat-body-title {
|
||||||
|
@@ -23,6 +23,7 @@ import {
|
|||||||
} from "react-router-dom";
|
} from "react-router-dom";
|
||||||
import { SideBar } from "./sidebar";
|
import { SideBar } from "./sidebar";
|
||||||
import { useAppConfig } from "../store/config";
|
import { useAppConfig } from "../store/config";
|
||||||
|
import { useMaskStore } from "../store/mask";
|
||||||
|
|
||||||
export function Loading(props: { noLogo?: boolean }) {
|
export function Loading(props: { noLogo?: boolean }) {
|
||||||
return (
|
return (
|
||||||
|
@@ -7,12 +7,67 @@ import RemarkGfm from "remark-gfm";
|
|||||||
import RehypeHighlight from "rehype-highlight";
|
import RehypeHighlight from "rehype-highlight";
|
||||||
import { useRef, useState, RefObject, useEffect } from "react";
|
import { useRef, useState, RefObject, useEffect } from "react";
|
||||||
import { copyToClipboard } from "../utils";
|
import { copyToClipboard } from "../utils";
|
||||||
|
import mermaid from "mermaid";
|
||||||
|
|
||||||
import LoadingIcon from "../icons/three-dots.svg";
|
import LoadingIcon from "../icons/three-dots.svg";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
|
export function Mermaid(props: { code: string; onError: () => void }) {
|
||||||
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (props.code && ref.current) {
|
||||||
|
mermaid
|
||||||
|
.run({
|
||||||
|
nodes: [ref.current],
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
props.onError();
|
||||||
|
console.error("[Mermaid] ", e.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [props.code]);
|
||||||
|
|
||||||
|
function viewSvgInNewWindow() {
|
||||||
|
const svg = ref.current?.querySelector("svg");
|
||||||
|
if (!svg) return;
|
||||||
|
const text = new XMLSerializer().serializeToString(svg);
|
||||||
|
const blob = new Blob([text], { type: "image/svg+xml" });
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const win = window.open(url);
|
||||||
|
if (win) {
|
||||||
|
win.onload = () => URL.revokeObjectURL(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="no-dark"
|
||||||
|
style={{ cursor: "pointer", overflow: "auto" }}
|
||||||
|
ref={ref}
|
||||||
|
onClick={() => viewSvgInNewWindow()}
|
||||||
|
>
|
||||||
|
{props.code}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function PreCode(props: { children: any }) {
|
export function PreCode(props: { children: any }) {
|
||||||
const ref = useRef<HTMLPreElement>(null);
|
const ref = useRef<HTMLPreElement>(null);
|
||||||
|
const [mermaidCode, setMermaidCode] = useState("");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!ref.current) return;
|
||||||
|
const mermaidDom = ref.current.querySelector("code.language-mermaid");
|
||||||
|
if (mermaidDom) {
|
||||||
|
setMermaidCode((mermaidDom as HTMLElement).innerText);
|
||||||
|
}
|
||||||
|
}, [props.children]);
|
||||||
|
|
||||||
|
if (mermaidCode) {
|
||||||
|
return <Mermaid code={mermaidCode} onError={() => setMermaidCode("")} />;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<pre ref={ref}>
|
<pre ref={ref}>
|
||||||
@@ -82,10 +137,12 @@ export function Markdown(
|
|||||||
const parentBounds = parent.getBoundingClientRect();
|
const parentBounds = parent.getBoundingClientRect();
|
||||||
const twoScreenHeight = Math.max(500, parentBounds.height * 2);
|
const twoScreenHeight = Math.max(500, parentBounds.height * 2);
|
||||||
const mdBounds = md.getBoundingClientRect();
|
const mdBounds = md.getBoundingClientRect();
|
||||||
const isInRange = (x: number) =>
|
const parentTop = parentBounds.top - twoScreenHeight;
|
||||||
x <= parentBounds.bottom + twoScreenHeight &&
|
const parentBottom = parentBounds.bottom + twoScreenHeight;
|
||||||
x >= parentBounds.top - twoScreenHeight;
|
const isOverlap =
|
||||||
inView.current = isInRange(mdBounds.top) || isInRange(mdBounds.bottom);
|
Math.max(parentTop, mdBounds.top) <=
|
||||||
|
Math.min(parentBottom, mdBounds.bottom);
|
||||||
|
inView.current = isOverlap;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inView.current && md) {
|
if (inView.current && md) {
|
||||||
@@ -96,7 +153,7 @@ export function Markdown(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
checkInView();
|
setTimeout(() => checkInView(), 1);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
@@ -20,8 +20,8 @@ import Locale, { AllLangs, Lang } from "../locales";
|
|||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
import chatStyle from "./chat.module.scss";
|
import chatStyle from "./chat.module.scss";
|
||||||
import { useEffect, useState } from "react";
|
import { useState } from "react";
|
||||||
import { downloadAs } from "../utils";
|
import { downloadAs, readFromFile } from "../utils";
|
||||||
import { Updater } from "../api/openai/typing";
|
import { Updater } from "../api/openai/typing";
|
||||||
import { ModelConfigList } from "./model-config";
|
import { ModelConfigList } from "./model-config";
|
||||||
import { FileName, Path } from "../constant";
|
import { FileName, Path } from "../constant";
|
||||||
@@ -106,6 +106,59 @@ export function MaskConfig(props: {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ContextPromptItem(props: {
|
||||||
|
prompt: Message;
|
||||||
|
update: (prompt: Message) => void;
|
||||||
|
remove: () => void;
|
||||||
|
}) {
|
||||||
|
const [focusingInput, setFocusingInput] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={chatStyle["context-prompt-row"]}>
|
||||||
|
{!focusingInput && (
|
||||||
|
<select
|
||||||
|
value={props.prompt.role}
|
||||||
|
className={chatStyle["context-role"]}
|
||||||
|
onChange={(e) =>
|
||||||
|
props.update({
|
||||||
|
...props.prompt,
|
||||||
|
role: e.target.value as any,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{ROLES.map((r) => (
|
||||||
|
<option key={r} value={r}>
|
||||||
|
{r}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
)}
|
||||||
|
<Input
|
||||||
|
value={props.prompt.content}
|
||||||
|
type="text"
|
||||||
|
className={chatStyle["context-content"]}
|
||||||
|
rows={focusingInput ? 5 : 1}
|
||||||
|
onFocus={() => setFocusingInput(true)}
|
||||||
|
onBlur={() => setFocusingInput(false)}
|
||||||
|
onInput={(e) =>
|
||||||
|
props.update({
|
||||||
|
...props.prompt,
|
||||||
|
content: e.currentTarget.value as any,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
{!focusingInput && (
|
||||||
|
<IconButton
|
||||||
|
icon={<DeleteIcon />}
|
||||||
|
className={chatStyle["context-delete-button"]}
|
||||||
|
onClick={() => props.remove()}
|
||||||
|
bordered
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function ContextPrompts(props: {
|
export function ContextPrompts(props: {
|
||||||
context: Message[];
|
context: Message[];
|
||||||
updateContext: (updater: (context: Message[]) => void) => void;
|
updateContext: (updater: (context: Message[]) => void) => void;
|
||||||
@@ -128,42 +181,12 @@ export function ContextPrompts(props: {
|
|||||||
<>
|
<>
|
||||||
<div className={chatStyle["context-prompt"]} style={{ marginBottom: 20 }}>
|
<div className={chatStyle["context-prompt"]} style={{ marginBottom: 20 }}>
|
||||||
{context.map((c, i) => (
|
{context.map((c, i) => (
|
||||||
<div className={chatStyle["context-prompt-row"]} key={i}>
|
<ContextPromptItem
|
||||||
<select
|
key={i}
|
||||||
value={c.role}
|
prompt={c}
|
||||||
className={chatStyle["context-role"]}
|
update={(prompt) => updateContextPrompt(i, prompt)}
|
||||||
onChange={(e) =>
|
remove={() => removeContextPrompt(i)}
|
||||||
updateContextPrompt(i, {
|
/>
|
||||||
...c,
|
|
||||||
role: e.target.value as any,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{ROLES.map((r) => (
|
|
||||||
<option key={r} value={r}>
|
|
||||||
{r}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
<Input
|
|
||||||
value={c.content}
|
|
||||||
type="text"
|
|
||||||
className={chatStyle["context-content"]}
|
|
||||||
rows={1}
|
|
||||||
onInput={(e) =>
|
|
||||||
updateContextPrompt(i, {
|
|
||||||
...c,
|
|
||||||
content: e.currentTarget.value as any,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<IconButton
|
|
||||||
icon={<DeleteIcon />}
|
|
||||||
className={chatStyle["context-delete-button"]}
|
|
||||||
onClick={() => removeContextPrompt(i)}
|
|
||||||
bordered
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
))}
|
))}
|
||||||
|
|
||||||
<div className={chatStyle["context-prompt-row"]}>
|
<div className={chatStyle["context-prompt-row"]}>
|
||||||
@@ -174,7 +197,7 @@ export function ContextPrompts(props: {
|
|||||||
className={chatStyle["context-prompt-button"]}
|
className={chatStyle["context-prompt-button"]}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
addContextPrompt({
|
addContextPrompt({
|
||||||
role: "system",
|
role: "user",
|
||||||
content: "",
|
content: "",
|
||||||
date: "",
|
date: "",
|
||||||
})
|
})
|
||||||
@@ -222,6 +245,21 @@ export function MaskPage() {
|
|||||||
downloadAs(JSON.stringify(masks), FileName.Masks);
|
downloadAs(JSON.stringify(masks), FileName.Masks);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const importFromFile = () => {
|
||||||
|
readFromFile().then((content) => {
|
||||||
|
try {
|
||||||
|
const importMasks = JSON.parse(content);
|
||||||
|
if (Array.isArray(importMasks)) {
|
||||||
|
for (const mask of importMasks) {
|
||||||
|
if (mask.name) {
|
||||||
|
maskStore.create(mask);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<div className={styles["mask-page"]}>
|
<div className={styles["mask-page"]}>
|
||||||
@@ -247,7 +285,7 @@ export function MaskPage() {
|
|||||||
<IconButton
|
<IconButton
|
||||||
icon={<UploadIcon />}
|
icon={<UploadIcon />}
|
||||||
bordered
|
bordered
|
||||||
onClick={() => showToast(Locale.WIP)}
|
onClick={() => importFromFile()}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="window-action-button">
|
<div className="window-action-button">
|
||||||
@@ -371,7 +409,10 @@ export function MaskPage() {
|
|||||||
key="export"
|
key="export"
|
||||||
bordered
|
bordered
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
downloadAs(JSON.stringify(editingMask), "mask.json")
|
downloadAs(
|
||||||
|
JSON.stringify(editingMask),
|
||||||
|
`${editingMask.name}.json`,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
/>,
|
/>,
|
||||||
<IconButton
|
<IconButton
|
||||||
|
@@ -1,4 +1,3 @@
|
|||||||
import styles from "./settings.module.scss";
|
|
||||||
import { ALL_MODELS, ModalConfigValidator, ModelConfig } from "../store";
|
import { ALL_MODELS, ModalConfigValidator, ModelConfig } from "../store";
|
||||||
|
|
||||||
import Locale from "../locales";
|
import Locale from "../locales";
|
||||||
|
@@ -13,6 +13,7 @@ import { Mask, useMaskStore } from "../store/mask";
|
|||||||
import Locale from "../locales";
|
import Locale from "../locales";
|
||||||
import { useAppConfig, useChatStore } from "../store";
|
import { useAppConfig, useChatStore } from "../store";
|
||||||
import { MaskAvatar } from "./mask";
|
import { MaskAvatar } from "./mask";
|
||||||
|
import { useCommand } from "../command";
|
||||||
|
|
||||||
function getIntersectionArea(aRect: DOMRect, bRect: DOMRect) {
|
function getIntersectionArea(aRect: DOMRect, bRect: DOMRect) {
|
||||||
const xmin = Math.max(aRect.x, bRect.x);
|
const xmin = Math.max(aRect.x, bRect.x);
|
||||||
@@ -108,9 +109,20 @@ export function NewChat() {
|
|||||||
|
|
||||||
const startChat = (mask?: Mask) => {
|
const startChat = (mask?: Mask) => {
|
||||||
chatStore.newSession(mask);
|
chatStore.newSession(mask);
|
||||||
navigate(Path.Chat);
|
setTimeout(() => navigate(Path.Chat), 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useCommand({
|
||||||
|
mask: (id) => {
|
||||||
|
try {
|
||||||
|
const mask = maskStore.get(parseInt(id));
|
||||||
|
startChat(mask ?? undefined);
|
||||||
|
} catch {
|
||||||
|
console.error("[New Chat] failed to create chat from mask id=", id);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles["new-chat"]}>
|
<div className={styles["new-chat"]}>
|
||||||
<div className={styles["mask-header"]}>
|
<div className={styles["mask-header"]}>
|
||||||
|
@@ -60,18 +60,13 @@
|
|||||||
.user-prompt-buttons {
|
.user-prompt-buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
column-gap: 2px;
|
||||||
|
|
||||||
.user-prompt-button {
|
.user-prompt-button {
|
||||||
height: 100%;
|
//height: 100%;
|
||||||
|
padding: 7px;
|
||||||
&:not(:last-child) {
|
|
||||||
margin-right: 5px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-prompt-actions {
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -7,6 +7,7 @@ import AddIcon from "../icons/add.svg";
|
|||||||
import CloseIcon from "../icons/close.svg";
|
import CloseIcon from "../icons/close.svg";
|
||||||
import CopyIcon from "../icons/copy.svg";
|
import CopyIcon from "../icons/copy.svg";
|
||||||
import ClearIcon from "../icons/clear.svg";
|
import ClearIcon from "../icons/clear.svg";
|
||||||
|
import LoadingIcon from "../icons/three-dots.svg";
|
||||||
import EditIcon from "../icons/edit.svg";
|
import EditIcon from "../icons/edit.svg";
|
||||||
import EyeIcon from "../icons/eye.svg";
|
import EyeIcon from "../icons/eye.svg";
|
||||||
import { Input, List, ListItem, Modal, PasswordInput, Popover } from "./ui-lib";
|
import { Input, List, ListItem, Modal, PasswordInput, Popover } from "./ui-lib";
|
||||||
@@ -183,6 +184,19 @@ function UserPromptModal(props: { onClose?: () => void }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatVersionDate(t: string) {
|
||||||
|
const d = new Date(+t);
|
||||||
|
const year = d.getUTCFullYear();
|
||||||
|
const month = d.getUTCMonth() + 1;
|
||||||
|
const day = d.getUTCDate();
|
||||||
|
|
||||||
|
return [
|
||||||
|
year.toString(),
|
||||||
|
month.toString().padStart(2, "0"),
|
||||||
|
day.toString().padStart(2, "0"),
|
||||||
|
].join("");
|
||||||
|
}
|
||||||
|
|
||||||
export function Settings() {
|
export function Settings() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [showEmojiPicker, setShowEmojiPicker] = useState(false);
|
const [showEmojiPicker, setShowEmojiPicker] = useState(false);
|
||||||
@@ -193,8 +207,8 @@ export function Settings() {
|
|||||||
|
|
||||||
const updateStore = useUpdateStore();
|
const updateStore = useUpdateStore();
|
||||||
const [checkingUpdate, setCheckingUpdate] = useState(false);
|
const [checkingUpdate, setCheckingUpdate] = useState(false);
|
||||||
const currentVersion = updateStore.version;
|
const currentVersion = formatVersionDate(updateStore.version);
|
||||||
const remoteId = updateStore.remoteVersion;
|
const remoteId = formatVersionDate(updateStore.remoteVersion);
|
||||||
const hasNewVersion = currentVersion !== remoteId;
|
const hasNewVersion = currentVersion !== remoteId;
|
||||||
|
|
||||||
function checkUpdate(force = false) {
|
function checkUpdate(force = false) {
|
||||||
@@ -202,6 +216,15 @@ export function Settings() {
|
|||||||
updateStore.getLatestVersion(force).then(() => {
|
updateStore.getLatestVersion(force).then(() => {
|
||||||
setCheckingUpdate(false);
|
setCheckingUpdate(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
"[Update] local version ",
|
||||||
|
new Date(+updateStore.version).toLocaleString(),
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
"[Update] remote version ",
|
||||||
|
new Date(+updateStore.remoteVersion).toLocaleString(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const usage = {
|
const usage = {
|
||||||
@@ -330,7 +353,7 @@ export function Settings() {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
{checkingUpdate ? (
|
{checkingUpdate ? (
|
||||||
<div />
|
<LoadingIcon />
|
||||||
) : hasNewVersion ? (
|
) : hasNewVersion ? (
|
||||||
<Link href={UPDATE_URL} target="_blank" className="link">
|
<Link href={UPDATE_URL} target="_blank" className="link">
|
||||||
{Locale.Settings.Update.GoToUpdate}
|
{Locale.Settings.Update.GoToUpdate}
|
||||||
@@ -466,19 +489,21 @@ export function Settings() {
|
|||||||
<></>
|
<></>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<ListItem
|
{!accessStore.hideUserApiKey ? (
|
||||||
title={Locale.Settings.Token.Title}
|
<ListItem
|
||||||
subTitle={Locale.Settings.Token.SubTitle}
|
title={Locale.Settings.Token.Title}
|
||||||
>
|
subTitle={Locale.Settings.Token.SubTitle}
|
||||||
<PasswordInput
|
>
|
||||||
value={accessStore.token}
|
<PasswordInput
|
||||||
type="text"
|
value={accessStore.token}
|
||||||
placeholder={Locale.Settings.Token.Placeholder}
|
type="text"
|
||||||
onChange={(e) => {
|
placeholder={Locale.Settings.Token.Placeholder}
|
||||||
accessStore.updateToken(e.currentTarget.value);
|
onChange={(e) => {
|
||||||
}}
|
accessStore.updateToken(e.currentTarget.value);
|
||||||
/>
|
}}
|
||||||
</ListItem>
|
/>
|
||||||
|
</ListItem>
|
||||||
|
) : null}
|
||||||
|
|
||||||
<ListItem
|
<ListItem
|
||||||
title={Locale.Settings.Usage.Title}
|
title={Locale.Settings.Usage.Title}
|
||||||
|
@@ -163,6 +163,7 @@ export function SideBar(props: { className?: string }) {
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (config.dontShowMaskSplashScreen) {
|
if (config.dontShowMaskSplashScreen) {
|
||||||
chatStore.newSession();
|
chatStore.newSession();
|
||||||
|
navigate(Path.Chat);
|
||||||
} else {
|
} else {
|
||||||
navigate(Path.NewChat);
|
navigate(Path.NewChat);
|
||||||
}
|
}
|
||||||
|
@@ -124,6 +124,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 600px) {
|
||||||
|
.modal-container {
|
||||||
|
width: 100vw;
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
max-height: 50vh;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.show {
|
.show {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
transition: all ease 0.3s;
|
transition: all ease 0.3s;
|
||||||
@@ -191,13 +203,3 @@
|
|||||||
resize: none;
|
resize: none;
|
||||||
min-width: 50px;
|
min-width: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 600px) {
|
|
||||||
.modal-container {
|
|
||||||
width: 90vw;
|
|
||||||
|
|
||||||
.modal-content {
|
|
||||||
max-height: 50vh;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@@ -1,13 +1,10 @@
|
|||||||
const COMMIT_ID: string = (() => {
|
const COMMIT_ID: string = (() => {
|
||||||
try {
|
try {
|
||||||
const childProcess = require("child_process");
|
const childProcess = require("child_process");
|
||||||
return (
|
return childProcess
|
||||||
childProcess
|
.execSync('git log -1 --format="%at000" --date=unix')
|
||||||
// .execSync("git describe --tags --abbrev=0")
|
.toString()
|
||||||
.execSync("git rev-parse --short HEAD")
|
.trim();
|
||||||
.toString()
|
|
||||||
.trim()
|
|
||||||
);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("[Build Config] No git or not from git repo.");
|
console.error("[Build Config] No git or not from git repo.");
|
||||||
return "unknown";
|
return "unknown";
|
||||||
|
@@ -7,6 +7,7 @@ declare global {
|
|||||||
CODE?: string;
|
CODE?: string;
|
||||||
PROXY_URL?: string;
|
PROXY_URL?: string;
|
||||||
VERCEL?: string;
|
VERCEL?: string;
|
||||||
|
HIDE_USER_API_KEY?: string; // disable user's api key input
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -38,5 +39,6 @@ export const getServerSideConfig = () => {
|
|||||||
needCode: ACCESS_CODES.size > 0,
|
needCode: ACCESS_CODES.size > 0,
|
||||||
proxyUrl: process.env.PROXY_URL,
|
proxyUrl: process.env.PROXY_URL,
|
||||||
isVercel: !!process.env.VERCEL,
|
isVercel: !!process.env.VERCEL,
|
||||||
|
hideUserApiKey: !!process.env.HIDE_USER_API_KEY,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@@ -36,3 +36,5 @@ export enum StoreKey {
|
|||||||
export const MAX_SIDEBAR_WIDTH = 500;
|
export const MAX_SIDEBAR_WIDTH = 500;
|
||||||
export const MIN_SIDEBAR_WIDTH = 230;
|
export const MIN_SIDEBAR_WIDTH = 230;
|
||||||
export const NARROW_SIDEBAR_WIDTH = 100;
|
export const NARROW_SIDEBAR_WIDTH = 100;
|
||||||
|
|
||||||
|
export const ACCESS_CODE_PREFIX = "ak-";
|
||||||
|
@@ -35,10 +35,9 @@ export default function RootLayout({
|
|||||||
/>
|
/>
|
||||||
<meta name="version" content={buildConfig.commitId} />
|
<meta name="version" content={buildConfig.commitId} />
|
||||||
<link rel="manifest" href="/site.webmanifest"></link>
|
<link rel="manifest" href="/site.webmanifest"></link>
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com"></link>
|
<link rel="preconnect" href="https://fonts.proxy.ustclug.org"></link>
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com"></link>
|
|
||||||
<link
|
<link
|
||||||
href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;700;900&display=swap"
|
href="https://fonts.proxy.ustclug.org/css2?family=Noto+Sans+SC:wght@300;400;700;900&display=swap"
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
></link>
|
></link>
|
||||||
<script src="/serviceWorkerRegister.js" defer></script>
|
<script src="/serviceWorkerRegister.js" defer></script>
|
||||||
|
@@ -4,7 +4,7 @@ const cn = {
|
|||||||
WIP: "该功能仍在开发中……",
|
WIP: "该功能仍在开发中……",
|
||||||
Error: {
|
Error: {
|
||||||
Unauthorized:
|
Unauthorized:
|
||||||
"现在是未授权状态,请点击左下角[设置](/#/settings)按钮输入访问密码。",
|
"访问密码不正确或为空,请前往[设置](/#/settings)页输入正确的访问密码,或者填入你自己的 OpenAI API Key。",
|
||||||
},
|
},
|
||||||
ChatItem: {
|
ChatItem: {
|
||||||
ChatItemCount: (count: number) => `${count} 条对话`,
|
ChatItemCount: (count: number) => `${count} 条对话`,
|
||||||
@@ -78,6 +78,7 @@ const cn = {
|
|||||||
tr: "Türkçe",
|
tr: "Türkçe",
|
||||||
jp: "日本語",
|
jp: "日本語",
|
||||||
de: "Deutsch",
|
de: "Deutsch",
|
||||||
|
vi: "Vietnamese",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Avatar: "头像",
|
Avatar: "头像",
|
||||||
@@ -148,7 +149,7 @@ const cn = {
|
|||||||
},
|
},
|
||||||
AccessCode: {
|
AccessCode: {
|
||||||
Title: "访问密码",
|
Title: "访问密码",
|
||||||
SubTitle: "已开启加密访问",
|
SubTitle: "管理员已开启加密访问",
|
||||||
Placeholder: "请输入访问密码",
|
Placeholder: "请输入访问密码",
|
||||||
},
|
},
|
||||||
Model: "模型 (model)",
|
Model: "模型 (model)",
|
||||||
|
@@ -81,6 +81,7 @@ const de: LocaleType = {
|
|||||||
tr: "Türkçe",
|
tr: "Türkçe",
|
||||||
jp: "日本語",
|
jp: "日本語",
|
||||||
de: "Deutsch",
|
de: "Deutsch",
|
||||||
|
vi: "Vietnamese",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Avatar: "Avatar",
|
Avatar: "Avatar",
|
||||||
|
@@ -80,6 +80,7 @@ const en: LocaleType = {
|
|||||||
tr: "Türkçe",
|
tr: "Türkçe",
|
||||||
jp: "日本語",
|
jp: "日本語",
|
||||||
de: "Deutsch",
|
de: "Deutsch",
|
||||||
|
vi: "Vietnamese",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Avatar: "Avatar",
|
Avatar: "Avatar",
|
||||||
|
@@ -80,6 +80,7 @@ const es: LocaleType = {
|
|||||||
tr: "Türkçe",
|
tr: "Türkçe",
|
||||||
jp: "日本語",
|
jp: "日本語",
|
||||||
de: "Deutsch",
|
de: "Deutsch",
|
||||||
|
vi: "Vietnamese",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Avatar: "Avatar",
|
Avatar: "Avatar",
|
||||||
|
@@ -6,6 +6,7 @@ import IT from "./it";
|
|||||||
import TR from "./tr";
|
import TR from "./tr";
|
||||||
import JP from "./jp";
|
import JP from "./jp";
|
||||||
import DE from "./de";
|
import DE from "./de";
|
||||||
|
import VI from "./vi";
|
||||||
|
|
||||||
export type { LocaleType } from "./cn";
|
export type { LocaleType } from "./cn";
|
||||||
|
|
||||||
@@ -18,10 +19,12 @@ export const AllLangs = [
|
|||||||
"tr",
|
"tr",
|
||||||
"jp",
|
"jp",
|
||||||
"de",
|
"de",
|
||||||
|
"vi",
|
||||||
] as const;
|
] as const;
|
||||||
export type Lang = (typeof AllLangs)[number];
|
export type Lang = (typeof AllLangs)[number];
|
||||||
|
|
||||||
const LANG_KEY = "lang";
|
const LANG_KEY = "lang";
|
||||||
|
const DEFAULT_LANG = "en";
|
||||||
|
|
||||||
function getItem(key: string) {
|
function getItem(key: string) {
|
||||||
try {
|
try {
|
||||||
@@ -41,7 +44,8 @@ function getLanguage() {
|
|||||||
try {
|
try {
|
||||||
return navigator.language.toLowerCase();
|
return navigator.language.toLowerCase();
|
||||||
} catch {
|
} catch {
|
||||||
return "cn";
|
console.log("[Lang] failed to detect user lang.");
|
||||||
|
return DEFAULT_LANG;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,7 +64,7 @@ export function getLang(): Lang {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return "en";
|
return DEFAULT_LANG;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function changeLang(lang: Lang) {
|
export function changeLang(lang: Lang) {
|
||||||
@@ -77,4 +81,5 @@ export default {
|
|||||||
tr: TR,
|
tr: TR,
|
||||||
jp: JP,
|
jp: JP,
|
||||||
de: DE,
|
de: DE,
|
||||||
|
vi: VI,
|
||||||
}[getLang()] as typeof CN;
|
}[getLang()] as typeof CN;
|
||||||
|
@@ -80,6 +80,7 @@ const it: LocaleType = {
|
|||||||
tr: "Türkçe",
|
tr: "Türkçe",
|
||||||
jp: "日本語",
|
jp: "日本語",
|
||||||
de: "Deutsch",
|
de: "Deutsch",
|
||||||
|
vi: "Vietnamese",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Avatar: "Avatar",
|
Avatar: "Avatar",
|
||||||
|
@@ -80,6 +80,7 @@ const jp: LocaleType = {
|
|||||||
tr: "Türkçe",
|
tr: "Türkçe",
|
||||||
jp: "日本語",
|
jp: "日本語",
|
||||||
de: "Deutsch",
|
de: "Deutsch",
|
||||||
|
vi: "Vietnamese",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Avatar: "アバター",
|
Avatar: "アバター",
|
||||||
|
@@ -80,6 +80,7 @@ const tr: LocaleType = {
|
|||||||
tr: "Türkçe",
|
tr: "Türkçe",
|
||||||
jp: "日本語",
|
jp: "日本語",
|
||||||
de: "Deutsch",
|
de: "Deutsch",
|
||||||
|
vi: "Vietnamese",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Avatar: "Avatar",
|
Avatar: "Avatar",
|
||||||
|
@@ -78,6 +78,7 @@ const tw: LocaleType = {
|
|||||||
tr: "Türkçe",
|
tr: "Türkçe",
|
||||||
jp: "日本語",
|
jp: "日本語",
|
||||||
de: "Deutsch",
|
de: "Deutsch",
|
||||||
|
vi: "Vietnamese",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Avatar: "大頭貼",
|
Avatar: "大頭貼",
|
||||||
|
241
app/locales/vi.ts
Normal file
241
app/locales/vi.ts
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
import { SubmitKey } from "../store/config";
|
||||||
|
import type { LocaleType } from "./index";
|
||||||
|
|
||||||
|
const vi: LocaleType = {
|
||||||
|
WIP: "Coming Soon...",
|
||||||
|
Error: {
|
||||||
|
Unauthorized:
|
||||||
|
"Truy cập chưa xác thực, vui lòng nhập mã truy cập trong trang cài đặt.",
|
||||||
|
},
|
||||||
|
ChatItem: {
|
||||||
|
ChatItemCount: (count: number) => `${count} tin nhắn`,
|
||||||
|
},
|
||||||
|
Chat: {
|
||||||
|
SubTitle: (count: number) => `${count} tin nhắn với ChatGPT`,
|
||||||
|
Actions: {
|
||||||
|
ChatList: "Xem danh sách chat",
|
||||||
|
CompressedHistory: "Nén tin nhắn trong quá khứ",
|
||||||
|
Export: "Xuất tất cả tin nhắn dưới dạng Markdown",
|
||||||
|
Copy: "Sao chép",
|
||||||
|
Stop: "Dừng",
|
||||||
|
Retry: "Thử lại",
|
||||||
|
Delete: "Xóa",
|
||||||
|
},
|
||||||
|
Rename: "Đổi tên",
|
||||||
|
Typing: "Đang nhập…",
|
||||||
|
Input: (submitKey: string) => {
|
||||||
|
var inputHints = `${submitKey} để gửi`;
|
||||||
|
if (submitKey === String(SubmitKey.Enter)) {
|
||||||
|
inputHints += ", Shift + Enter để xuống dòng";
|
||||||
|
}
|
||||||
|
return inputHints + ", / để tìm kiếm mẫu gợi ý";
|
||||||
|
},
|
||||||
|
Send: "Gửi",
|
||||||
|
Config: {
|
||||||
|
Reset: "Khôi phục cài đặt gốc",
|
||||||
|
SaveAs: "Lưu dưới dạng Mẫu",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Export: {
|
||||||
|
Title: "Tất cả tin nhắn",
|
||||||
|
Copy: "Sao chép tất cả",
|
||||||
|
Download: "Tải xuống",
|
||||||
|
MessageFromYou: "Tin nhắn của bạn",
|
||||||
|
MessageFromChatGPT: "Tin nhắn từ ChatGPT",
|
||||||
|
},
|
||||||
|
Memory: {
|
||||||
|
Title: "Lịch sử tin nhắn",
|
||||||
|
EmptyContent: "Chưa có tin nhắn",
|
||||||
|
Send: "Gửi tin nhắn trong quá khứ",
|
||||||
|
Copy: "Sao chép tin nhắn trong quá khứ",
|
||||||
|
Reset: "Đặt lại phiên",
|
||||||
|
ResetConfirm:
|
||||||
|
"Đặt lại sẽ xóa toàn bộ lịch sử trò chuyện hiện tại và bộ nhớ. Bạn có chắc chắn muốn đặt lại không?",
|
||||||
|
},
|
||||||
|
Home: {
|
||||||
|
NewChat: "Cuộc trò chuyện mới",
|
||||||
|
DeleteChat: "Xác nhận xóa các cuộc trò chuyện đã chọn?",
|
||||||
|
DeleteToast: "Đã xóa cuộc trò chuyện",
|
||||||
|
Revert: "Khôi phục",
|
||||||
|
},
|
||||||
|
Settings: {
|
||||||
|
Title: "Cài đặt",
|
||||||
|
SubTitle: "Tất cả cài đặt",
|
||||||
|
Actions: {
|
||||||
|
ClearAll: "Xóa toàn bộ dữ liệu",
|
||||||
|
ResetAll: "Khôi phục cài đặt gốc",
|
||||||
|
Close: "Đóng",
|
||||||
|
ConfirmResetAll: "Bạn chắc chắn muốn thiết lập lại tất cả cài đặt?",
|
||||||
|
ConfirmClearAll: "Bạn chắc chắn muốn thiết lập lại tất cả dữ liệu?",
|
||||||
|
},
|
||||||
|
Lang: {
|
||||||
|
Name: "Language", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language`
|
||||||
|
All: "Tất cả ngôn ngữ",
|
||||||
|
Options: {
|
||||||
|
cn: "简体中文",
|
||||||
|
en: "English",
|
||||||
|
tw: "繁體中文",
|
||||||
|
es: "Español",
|
||||||
|
it: "Italiano",
|
||||||
|
tr: "Türkçe",
|
||||||
|
jp: "日本語",
|
||||||
|
de: "Deutsch",
|
||||||
|
vi: "Vietnamese",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Avatar: "Ảnh đại diện",
|
||||||
|
FontSize: {
|
||||||
|
Title: "Font chữ",
|
||||||
|
SubTitle: "Thay đổi font chữ của nội dung trò chuyện",
|
||||||
|
},
|
||||||
|
Update: {
|
||||||
|
Version: (x: string) => `Phiên bản: ${x}`,
|
||||||
|
IsLatest: "Phiên bản mới nhất",
|
||||||
|
CheckUpdate: "Kiểm tra bản cập nhật",
|
||||||
|
IsChecking: "Kiểm tra bản cập nhật...",
|
||||||
|
FoundUpdate: (x: string) => `Phát hiện phiên bản mới: ${x}`,
|
||||||
|
GoToUpdate: "Cập nhật",
|
||||||
|
},
|
||||||
|
SendKey: "Phím gửi",
|
||||||
|
Theme: "Theme",
|
||||||
|
TightBorder: "Chế độ không viền",
|
||||||
|
SendPreviewBubble: {
|
||||||
|
Title: "Gửi bong bóng xem trước",
|
||||||
|
SubTitle: "Xem trước nội dung markdown bằng bong bóng",
|
||||||
|
},
|
||||||
|
Mask: {
|
||||||
|
Title: "Mask Splash Screen",
|
||||||
|
SubTitle: "Chớp màn hình khi bắt đầu cuộc trò chuyện mới",
|
||||||
|
},
|
||||||
|
Prompt: {
|
||||||
|
Disable: {
|
||||||
|
Title: "Vô hiệu hóa chức năng tự động hoàn thành",
|
||||||
|
SubTitle: "Nhập / để kích hoạt chức năng tự động hoàn thành",
|
||||||
|
},
|
||||||
|
List: "Danh sách mẫu gợi ý",
|
||||||
|
ListCount: (builtin: number, custom: number) =>
|
||||||
|
`${builtin} có sẵn, ${custom} do người dùng xác định`,
|
||||||
|
Edit: "Chỉnh sửa",
|
||||||
|
Modal: {
|
||||||
|
Title: "Danh sách mẫu gợi ý",
|
||||||
|
Add: "Thêm",
|
||||||
|
Search: "Tìm kiếm mẫu",
|
||||||
|
},
|
||||||
|
EditModal: {
|
||||||
|
Title: "Chỉnh sửa mẫu",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
HistoryCount: {
|
||||||
|
Title: "Số lượng tin nhắn đính kèm",
|
||||||
|
SubTitle: "Số lượng tin nhắn trong quá khứ được gửi kèm theo mỗi yêu cầu",
|
||||||
|
},
|
||||||
|
CompressThreshold: {
|
||||||
|
Title: "Ngưỡng nén lịch sử tin nhắn",
|
||||||
|
SubTitle: "Thực hiện nén nếu số lượng tin nhắn chưa nén vượt quá ngưỡng",
|
||||||
|
},
|
||||||
|
Token: {
|
||||||
|
Title: "API Key",
|
||||||
|
SubTitle: "Sử dụng khóa của bạn để bỏ qua giới hạn mã truy cập",
|
||||||
|
Placeholder: "OpenAI API Key",
|
||||||
|
},
|
||||||
|
Usage: {
|
||||||
|
Title: "Hạn mức tài khoản",
|
||||||
|
SubTitle(used: any, total: any) {
|
||||||
|
return `Đã sử dụng $${used} trong tháng này, hạn mức $${total}`;
|
||||||
|
},
|
||||||
|
IsChecking: "Đang kiểm tra...",
|
||||||
|
Check: "Kiểm tra",
|
||||||
|
NoAccess: "Nhập API Key để kiểm tra hạn mức",
|
||||||
|
},
|
||||||
|
AccessCode: {
|
||||||
|
Title: "Mã truy cập",
|
||||||
|
SubTitle: "Đã bật kiểm soát truy cập",
|
||||||
|
Placeholder: "Nhập mã truy cập",
|
||||||
|
},
|
||||||
|
Model: "Mô hình",
|
||||||
|
Temperature: {
|
||||||
|
Title: "Tính ngẫu nhiên (temperature)",
|
||||||
|
SubTitle: "Giá trị càng lớn, câu trả lời càng ngẫu nhiên",
|
||||||
|
},
|
||||||
|
MaxTokens: {
|
||||||
|
Title: "Giới hạn số lượng token (max_tokens)",
|
||||||
|
SubTitle: "Số lượng token tối đa được sử dụng trong mỗi lần tương tác",
|
||||||
|
},
|
||||||
|
PresencePenlty: {
|
||||||
|
Title: "Chủ đề mới (presence_penalty)",
|
||||||
|
SubTitle: "Giá trị càng lớn tăng khả năng mở rộng sang các chủ đề mới",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Store: {
|
||||||
|
DefaultTopic: "Cuộc trò chuyện mới",
|
||||||
|
BotHello: "Xin chào! Mình có thể giúp gì cho bạn?",
|
||||||
|
Error: "Có lỗi xảy ra, vui lòng thử lại sau.",
|
||||||
|
Prompt: {
|
||||||
|
History: (content: string) =>
|
||||||
|
"Tóm tắt ngắn gọn cuộc trò chuyện giữa người dùng và AI: " + content,
|
||||||
|
Topic:
|
||||||
|
"Sử dụng 4 đến 5 từ tóm tắt cuộc trò chuyện này mà không có phần mở đầu, dấu chấm câu, dấu ngoặc kép, dấu chấm, ký hiệu hoặc văn bản bổ sung nào. Loại bỏ các dấu ngoặc kép kèm theo.",
|
||||||
|
Summarize:
|
||||||
|
"Tóm tắt cuộc trò chuyện này một cách ngắn gọn trong 200 từ hoặc ít hơn để sử dụng làm gợi ý cho ngữ cảnh tiếp theo.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Copy: {
|
||||||
|
Success: "Sao chép vào bộ nhớ tạm",
|
||||||
|
Failed:
|
||||||
|
"Sao chép không thành công, vui lòng cấp quyền truy cập vào bộ nhớ tạm",
|
||||||
|
},
|
||||||
|
Context: {
|
||||||
|
Toast: (x: any) => `Sử dụng ${x} tin nhắn chứa ngữ cảnh`,
|
||||||
|
Edit: "Thiết lập ngữ cảnh và bộ nhớ",
|
||||||
|
Add: "Thêm tin nhắn",
|
||||||
|
},
|
||||||
|
Plugin: {
|
||||||
|
Name: "Plugin",
|
||||||
|
},
|
||||||
|
Mask: {
|
||||||
|
Name: "Mẫu",
|
||||||
|
Page: {
|
||||||
|
Title: "Mẫu trò chuyện",
|
||||||
|
SubTitle: (count: number) => `${count} mẫu`,
|
||||||
|
Search: "Tìm kiếm mẫu",
|
||||||
|
Create: "Tạo",
|
||||||
|
},
|
||||||
|
Item: {
|
||||||
|
Info: (count: number) => `${count} tin nhắn`,
|
||||||
|
Chat: "Chat",
|
||||||
|
View: "Xem trước",
|
||||||
|
Edit: "Chỉnh sửa",
|
||||||
|
Delete: "Xóa",
|
||||||
|
DeleteConfirm: "Xác nhận xóa?",
|
||||||
|
},
|
||||||
|
EditModal: {
|
||||||
|
Title: (readonly: boolean) =>
|
||||||
|
`Chỉnh sửa mẫu ${readonly ? "(chỉ xem)" : ""}`,
|
||||||
|
Download: "Tải xuống",
|
||||||
|
Clone: "Tạo bản sao",
|
||||||
|
},
|
||||||
|
Config: {
|
||||||
|
Avatar: "Ảnh đại diện bot",
|
||||||
|
Name: "Tên bot",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
NewChat: {
|
||||||
|
Return: "Quay lại",
|
||||||
|
Skip: "Bỏ qua",
|
||||||
|
Title: "Chọn 1 biểu tượng",
|
||||||
|
SubTitle: "Bắt đầu trò chuyện ẩn sau lớp mặt nạ",
|
||||||
|
More: "Tìm thêm",
|
||||||
|
NotShow: "Không hiển thị lại",
|
||||||
|
ConfirmNoShow: "Xác nhận tắt? Bạn có thể bật lại trong phần cài đặt.",
|
||||||
|
},
|
||||||
|
|
||||||
|
UI: {
|
||||||
|
Confirm: "Xác nhận",
|
||||||
|
Cancel: "Hủy",
|
||||||
|
Close: "Đóng",
|
||||||
|
Create: "Tạo",
|
||||||
|
Edit: "Chỉnh sửa",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default vi;
|
@@ -8,6 +8,7 @@ import {
|
|||||||
useChatStore,
|
useChatStore,
|
||||||
} from "./store";
|
} from "./store";
|
||||||
import { showToast } from "./components/ui-lib";
|
import { showToast } from "./components/ui-lib";
|
||||||
|
import { ACCESS_CODE_PREFIX } from "./constant";
|
||||||
|
|
||||||
const TIME_OUT_MS = 60000;
|
const TIME_OUT_MS = 60000;
|
||||||
|
|
||||||
@@ -46,27 +47,31 @@ function getHeaders() {
|
|||||||
const accessStore = useAccessStore.getState();
|
const accessStore = useAccessStore.getState();
|
||||||
let headers: Record<string, string> = {};
|
let headers: Record<string, string> = {};
|
||||||
|
|
||||||
if (accessStore.enabledAccessControl()) {
|
const makeBearer = (token: string) => `Bearer ${token.trim()}`;
|
||||||
headers["access-code"] = accessStore.accessCode;
|
const validString = (x: string) => x && x.length > 0;
|
||||||
}
|
|
||||||
|
|
||||||
if (accessStore.token && accessStore.token.length > 0) {
|
// use user's api key first
|
||||||
headers["token"] = accessStore.token;
|
if (validString(accessStore.token)) {
|
||||||
|
headers.Authorization = makeBearer(accessStore.token);
|
||||||
|
} else if (
|
||||||
|
accessStore.enabledAccessControl() &&
|
||||||
|
validString(accessStore.accessCode)
|
||||||
|
) {
|
||||||
|
headers.Authorization = makeBearer(
|
||||||
|
ACCESS_CODE_PREFIX + accessStore.accessCode,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return headers;
|
return headers;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function requestOpenaiClient(path: string) {
|
export function requestOpenaiClient(path: string) {
|
||||||
|
const openaiUrl = useAccessStore.getState().openaiUrl;
|
||||||
return (body: any, method = "POST") =>
|
return (body: any, method = "POST") =>
|
||||||
fetch("/api/openai", {
|
fetch(openaiUrl + path, {
|
||||||
method,
|
method,
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
path,
|
|
||||||
...getHeaders(),
|
|
||||||
},
|
|
||||||
body: body && JSON.stringify(body),
|
body: body && JSON.stringify(body),
|
||||||
|
headers: getHeaders(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,16 +166,17 @@ export async function requestChatStream(
|
|||||||
const reqTimeoutId = setTimeout(() => controller.abort(), TIME_OUT_MS);
|
const reqTimeoutId = setTimeout(() => controller.abort(), TIME_OUT_MS);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch("/api/chat-stream", {
|
const openaiUrl = useAccessStore.getState().openaiUrl;
|
||||||
|
const res = await fetch(openaiUrl + "v1/chat/completions", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
path: "v1/chat/completions",
|
|
||||||
...getHeaders(),
|
...getHeaders(),
|
||||||
},
|
},
|
||||||
body: JSON.stringify(req),
|
body: JSON.stringify(req),
|
||||||
signal: controller.signal,
|
signal: controller.signal,
|
||||||
});
|
});
|
||||||
|
|
||||||
clearTimeout(reqTimeoutId);
|
clearTimeout(reqTimeoutId);
|
||||||
|
|
||||||
let responseText = "";
|
let responseText = "";
|
||||||
|
@@ -1,12 +1,15 @@
|
|||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
import { persist } from "zustand/middleware";
|
import { persist } from "zustand/middleware";
|
||||||
import { StoreKey } from "../constant";
|
import { StoreKey } from "../constant";
|
||||||
|
import { BOT_HELLO } from "./chat";
|
||||||
|
|
||||||
export interface AccessControlStore {
|
export interface AccessControlStore {
|
||||||
accessCode: string;
|
accessCode: string;
|
||||||
token: string;
|
token: string;
|
||||||
|
|
||||||
needCode: boolean;
|
needCode: boolean;
|
||||||
|
hideUserApiKey: boolean;
|
||||||
|
openaiUrl: string;
|
||||||
|
|
||||||
updateToken: (_: string) => void;
|
updateToken: (_: string) => void;
|
||||||
updateCode: (_: string) => void;
|
updateCode: (_: string) => void;
|
||||||
@@ -23,18 +26,23 @@ export const useAccessStore = create<AccessControlStore>()(
|
|||||||
token: "",
|
token: "",
|
||||||
accessCode: "",
|
accessCode: "",
|
||||||
needCode: true,
|
needCode: true,
|
||||||
|
hideUserApiKey: false,
|
||||||
|
openaiUrl: "/api/openai/",
|
||||||
|
|
||||||
enabledAccessControl() {
|
enabledAccessControl() {
|
||||||
get().fetch();
|
get().fetch();
|
||||||
|
|
||||||
return get().needCode;
|
return get().needCode;
|
||||||
},
|
},
|
||||||
updateCode(code: string) {
|
updateCode(code: string) {
|
||||||
set((state) => ({ accessCode: code }));
|
set(() => ({ accessCode: code }));
|
||||||
},
|
},
|
||||||
updateToken(token: string) {
|
updateToken(token: string) {
|
||||||
set((state) => ({ token }));
|
set(() => ({ token }));
|
||||||
},
|
},
|
||||||
isAuthorized() {
|
isAuthorized() {
|
||||||
|
get().fetch();
|
||||||
|
|
||||||
// has token or has code or disabled access control
|
// has token or has code or disabled access control
|
||||||
return (
|
return (
|
||||||
!!get().token || !!get().accessCode || !get().enabledAccessControl()
|
!!get().token || !!get().accessCode || !get().enabledAccessControl()
|
||||||
@@ -51,6 +59,10 @@ export const useAccessStore = create<AccessControlStore>()(
|
|||||||
.then((res: DangerConfig) => {
|
.then((res: DangerConfig) => {
|
||||||
console.log("[Config] got config from server", res);
|
console.log("[Config] got config from server", res);
|
||||||
set(() => ({ ...res }));
|
set(() => ({ ...res }));
|
||||||
|
|
||||||
|
if ((res as any).botHello) {
|
||||||
|
BOT_HELLO.content = (res as any).botHello;
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
console.error("[Config] failed to fetch config");
|
console.error("[Config] failed to fetch config");
|
||||||
|
@@ -7,11 +7,11 @@ import {
|
|||||||
requestChatStream,
|
requestChatStream,
|
||||||
requestWithPrompt,
|
requestWithPrompt,
|
||||||
} from "../requests";
|
} from "../requests";
|
||||||
import { isMobileScreen, trimTopic } from "../utils";
|
import { trimTopic } from "../utils";
|
||||||
|
|
||||||
import Locale from "../locales";
|
import Locale from "../locales";
|
||||||
import { showToast } from "../components/ui-lib";
|
import { showToast } from "../components/ui-lib";
|
||||||
import { DEFAULT_CONFIG, ModelConfig, ModelType, useAppConfig } from "./config";
|
import { ModelType } from "./config";
|
||||||
import { createEmptyMask, Mask } from "./mask";
|
import { createEmptyMask, Mask } from "./mask";
|
||||||
import { StoreKey } from "../constant";
|
import { StoreKey } from "../constant";
|
||||||
|
|
||||||
|
@@ -31,7 +31,7 @@ export const DEFAULT_CONFIG = {
|
|||||||
|
|
||||||
modelConfig: {
|
modelConfig: {
|
||||||
model: "gpt-3.5-turbo" as ModelType,
|
model: "gpt-3.5-turbo" as ModelType,
|
||||||
temperature: 1,
|
temperature: 0.5,
|
||||||
max_tokens: 2000,
|
max_tokens: 2000,
|
||||||
presence_penalty: 0,
|
presence_penalty: 0,
|
||||||
sendMemory: true,
|
sendMemory: true,
|
||||||
|
@@ -57,6 +57,7 @@ export const useMaskStore = create<MaskStore>()(
|
|||||||
...createEmptyMask(),
|
...createEmptyMask(),
|
||||||
...mask,
|
...mask,
|
||||||
id,
|
id,
|
||||||
|
builtin: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
set(() => ({ masks }));
|
set(() => ({ masks }));
|
||||||
|
@@ -53,10 +53,9 @@ export const useUpdateStore = create<UpdateStore>()(
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// const data = await (await fetch(FETCH_TAG_URL)).json();
|
|
||||||
// const remoteId = data[0].name as string;
|
|
||||||
const data = await (await fetch(FETCH_COMMIT_URL)).json();
|
const data = await (await fetch(FETCH_COMMIT_URL)).json();
|
||||||
const remoteId = (data[0].sha as string).substring(0, 7);
|
const remoteCommitTime = data[0].commit.committer.date;
|
||||||
|
const remoteId = new Date(remoteCommitTime).getTime().toString();
|
||||||
set(() => ({
|
set(() => ({
|
||||||
remoteVersion: remoteId,
|
remoteVersion: remoteId,
|
||||||
}));
|
}));
|
||||||
|
@@ -88,6 +88,9 @@
|
|||||||
}
|
}
|
||||||
html {
|
html {
|
||||||
height: var(--full-height);
|
height: var(--full-height);
|
||||||
|
|
||||||
|
font-family: "Noto Sans SC", "SF Pro SC", "SF Pro Text", "SF Pro Icons",
|
||||||
|
"PingFang SC", "Helvetica Neue", "Helvetica", "Arial", sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
@@ -102,8 +105,6 @@ body {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
touch-action: pan-x pan-y;
|
touch-action: pan-x pan-y;
|
||||||
font-family: "Noto Sans SC", "SF Pro SC", "SF Pro Text", "SF Pro Icons",
|
|
||||||
"PingFang SC", "Helvetica Neue", "Helvetica", "Arial", sans-serif;
|
|
||||||
|
|
||||||
@media only screen and (max-width: 600px) {
|
@media only screen and (max-width: 600px) {
|
||||||
background-color: var(--second);
|
background-color: var(--second);
|
||||||
@@ -247,6 +248,10 @@ div.math {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
|
@media screen and (max-width: 600px) {
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.link {
|
.link {
|
||||||
|
20
app/utils.ts
20
app/utils.ts
@@ -42,6 +42,26 @@ export function downloadAs(text: string, filename: string) {
|
|||||||
document.body.removeChild(element);
|
document.body.removeChild(element);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function readFromFile() {
|
||||||
|
return new Promise<string>((res, rej) => {
|
||||||
|
const fileInput = document.createElement("input");
|
||||||
|
fileInput.type = "file";
|
||||||
|
fileInput.accept = "application/json";
|
||||||
|
|
||||||
|
fileInput.onchange = (event: any) => {
|
||||||
|
const file = event.target.files[0];
|
||||||
|
const fileReader = new FileReader();
|
||||||
|
fileReader.onload = (e: any) => {
|
||||||
|
res(e.target.result);
|
||||||
|
};
|
||||||
|
fileReader.onerror = (e) => rej(e);
|
||||||
|
fileReader.readAsText(file);
|
||||||
|
};
|
||||||
|
|
||||||
|
fileInput.click();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function isIOS() {
|
export function isIOS() {
|
||||||
const userAgent = navigator.userAgent.toLowerCase();
|
const userAgent = navigator.userAgent.toLowerCase();
|
||||||
return /iphone|ipad|ipod/.test(userAgent);
|
return /iphone|ipad|ipod/.test(userAgent);
|
||||||
|
@@ -1,72 +0,0 @@
|
|||||||
import { NextRequest, NextResponse } from "next/server";
|
|
||||||
import { getServerSideConfig } from "./app/config/server";
|
|
||||||
import md5 from "spark-md5";
|
|
||||||
|
|
||||||
export const config = {
|
|
||||||
matcher: ["/api/openai", "/api/chat-stream"],
|
|
||||||
};
|
|
||||||
|
|
||||||
const serverConfig = getServerSideConfig();
|
|
||||||
|
|
||||||
function getIP(req: NextRequest) {
|
|
||||||
let ip = req.ip ?? req.headers.get("x-real-ip");
|
|
||||||
const forwardedFor = req.headers.get("x-forwarded-for");
|
|
||||||
|
|
||||||
if (!ip && forwardedFor) {
|
|
||||||
ip = forwardedFor.split(",").at(0) ?? "";
|
|
||||||
}
|
|
||||||
|
|
||||||
return ip;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function middleware(req: NextRequest) {
|
|
||||||
const accessCode = req.headers.get("access-code");
|
|
||||||
const token = req.headers.get("token");
|
|
||||||
const hashedCode = md5.hash(accessCode ?? "").trim();
|
|
||||||
|
|
||||||
console.log("[Auth] allowed hashed codes: ", [...serverConfig.codes]);
|
|
||||||
console.log("[Auth] got access code:", accessCode);
|
|
||||||
console.log("[Auth] hashed access code:", hashedCode);
|
|
||||||
console.log("[User IP] ", getIP(req));
|
|
||||||
console.log("[Time] ", new Date().toLocaleString());
|
|
||||||
|
|
||||||
if (serverConfig.needCode && !serverConfig.codes.has(hashedCode) && !token) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{
|
|
||||||
error: true,
|
|
||||||
needAccessCode: true,
|
|
||||||
msg: "Please go settings page and fill your access code.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
status: 401,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// inject api key
|
|
||||||
if (!token) {
|
|
||||||
const apiKey = serverConfig.apiKey;
|
|
||||||
if (apiKey) {
|
|
||||||
console.log("[Auth] set system token");
|
|
||||||
req.headers.set("token", apiKey);
|
|
||||||
} else {
|
|
||||||
return NextResponse.json(
|
|
||||||
{
|
|
||||||
error: true,
|
|
||||||
msg: "Empty Api Key",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
status: 401,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.log("[Auth] set user token");
|
|
||||||
}
|
|
||||||
|
|
||||||
return NextResponse.next({
|
|
||||||
request: {
|
|
||||||
headers: req.headers,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
@@ -1,18 +0,0 @@
|
|||||||
/** @type {import('next').NextConfig} */
|
|
||||||
|
|
||||||
const nextConfig = {
|
|
||||||
experimental: {
|
|
||||||
appDir: true,
|
|
||||||
},
|
|
||||||
webpack(config) {
|
|
||||||
config.module.rules.push({
|
|
||||||
test: /\.svg$/,
|
|
||||||
use: ["@svgr/webpack"],
|
|
||||||
});
|
|
||||||
|
|
||||||
return config;
|
|
||||||
},
|
|
||||||
output: "standalone",
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = nextConfig;
|
|
39
next.config.mjs
Normal file
39
next.config.mjs
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
/** @type {import('next').NextConfig} */
|
||||||
|
|
||||||
|
const nextConfig = {
|
||||||
|
experimental: {
|
||||||
|
appDir: true,
|
||||||
|
},
|
||||||
|
async rewrites() {
|
||||||
|
const ret = [
|
||||||
|
{
|
||||||
|
source: "/api/proxy/:path*",
|
||||||
|
destination: "https://api.openai.com/:path*",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const apiUrl = process.env.API_URL;
|
||||||
|
if (apiUrl) {
|
||||||
|
console.log("[Next] using api url ", apiUrl);
|
||||||
|
ret.push({
|
||||||
|
source: "/api/:path*",
|
||||||
|
destination: `${apiUrl}/:path*`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
beforeFiles: ret,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
webpack(config) {
|
||||||
|
config.module.rules.push({
|
||||||
|
test: /\.svg$/,
|
||||||
|
use: ["@svgr/webpack"],
|
||||||
|
});
|
||||||
|
|
||||||
|
return config;
|
||||||
|
},
|
||||||
|
output: "standalone",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default nextConfig;
|
@@ -19,6 +19,7 @@
|
|||||||
"emoji-picker-react": "^4.4.7",
|
"emoji-picker-react": "^4.4.7",
|
||||||
"eventsource-parser": "^0.1.0",
|
"eventsource-parser": "^0.1.0",
|
||||||
"fuse.js": "^6.6.2",
|
"fuse.js": "^6.6.2",
|
||||||
|
"mermaid": "^10.1.0",
|
||||||
"next": "^13.3.1-canary.8",
|
"next": "^13.3.1-canary.8",
|
||||||
"node-fetch": "^3.3.1",
|
"node-fetch": "^3.3.1",
|
||||||
"openai": "^3.2.1",
|
"openai": "^3.2.1",
|
||||||
|
@@ -40,7 +40,7 @@ async function fetchEN() {
|
|||||||
return raw
|
return raw
|
||||||
.split("\n")
|
.split("\n")
|
||||||
.slice(1)
|
.slice(1)
|
||||||
.map((v) => v.split('","').map((v) => v.replace('"', "")));
|
.map((v) => v.split('","').map((v) => v.replace(/^"|"$/g, '').replaceAll('""','"')));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("[Fetch] failed to fetch en prompts", error);
|
console.error("[Fetch] failed to fetch en prompts", error);
|
||||||
return [];
|
return [];
|
||||||
|
446
yarn.lock
446
yarn.lock
@@ -995,6 +995,11 @@
|
|||||||
"@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"
|
||||||
|
|
||||||
|
"@braintree/sanitize-url@^6.0.0":
|
||||||
|
version "6.0.2"
|
||||||
|
resolved "https://registry.npmmirror.com/@braintree/sanitize-url/-/sanitize-url-6.0.2.tgz#6110f918d273fe2af8ea1c4398a88774bb9fc12f"
|
||||||
|
integrity sha512-Tbsj02wXCbqGmzdnXNk0SOF19ChhRU70BsroIi4Pm6Ehp56in6vch94mfbdQ17DozxkL3BAVjbZ4Qc1a0HFRAg==
|
||||||
|
|
||||||
"@eslint-community/eslint-utils@^4.2.0":
|
"@eslint-community/eslint-utils@^4.2.0":
|
||||||
version "4.4.0"
|
version "4.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59"
|
resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59"
|
||||||
@@ -1099,6 +1104,13 @@
|
|||||||
"@jridgewell/resolve-uri" "3.1.0"
|
"@jridgewell/resolve-uri" "3.1.0"
|
||||||
"@jridgewell/sourcemap-codec" "1.4.14"
|
"@jridgewell/sourcemap-codec" "1.4.14"
|
||||||
|
|
||||||
|
"@khanacademy/simple-markdown@^0.8.6":
|
||||||
|
version "0.8.6"
|
||||||
|
resolved "https://registry.npmmirror.com/@khanacademy/simple-markdown/-/simple-markdown-0.8.6.tgz#9c9aef1f5ce2ce60292d13849165965a57c26f25"
|
||||||
|
integrity sha512-mAUlR9lchzfqunR89pFvNI51jQKsMpJeWYsYWw0DQcUXczn/T/V6510utgvm7X0N3zN87j1SvuKk8cMbl9IAFw==
|
||||||
|
dependencies:
|
||||||
|
"@types/react" ">=16.0.0"
|
||||||
|
|
||||||
"@next/env@13.3.1-canary.8":
|
"@next/env@13.3.1-canary.8":
|
||||||
version "13.3.1-canary.8"
|
version "13.3.1-canary.8"
|
||||||
resolved "https://registry.yarnpkg.com/@next/env/-/env-13.3.1-canary.8.tgz#9f5cf57999e4f4b59ef6407924803a247cc4e451"
|
resolved "https://registry.yarnpkg.com/@next/env/-/env-13.3.1-canary.8.tgz#9f5cf57999e4f4b59ef6407924803a247cc4e451"
|
||||||
@@ -1399,6 +1411,15 @@
|
|||||||
"@types/scheduler" "*"
|
"@types/scheduler" "*"
|
||||||
csstype "^3.0.2"
|
csstype "^3.0.2"
|
||||||
|
|
||||||
|
"@types/react@>=16.0.0":
|
||||||
|
version "18.2.5"
|
||||||
|
resolved "https://registry.npmmirror.com/@types/react/-/react-18.2.5.tgz#f9403e1113b12b53f7edcdd9a900c10dd4b49a59"
|
||||||
|
integrity sha512-RuoMedzJ5AOh23Dvws13LU9jpZHIc/k90AgmK7CecAYeWmSr3553L4u5rk4sWAPBuQosfT7HmTfG4Rg5o4nGEA==
|
||||||
|
dependencies:
|
||||||
|
"@types/prop-types" "*"
|
||||||
|
"@types/scheduler" "*"
|
||||||
|
csstype "^3.0.2"
|
||||||
|
|
||||||
"@types/scheduler@*":
|
"@types/scheduler@*":
|
||||||
version "0.16.3"
|
version "0.16.3"
|
||||||
resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.3.tgz#cef09e3ec9af1d63d2a6cc5b383a737e24e6dcf5"
|
resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.3.tgz#cef09e3ec9af1d63d2a6cc5b383a737e24e6dcf5"
|
||||||
@@ -1871,16 +1892,16 @@ comma-separated-tokens@^2.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz#4e89c9458acb61bc8fef19f4529973b2392839ee"
|
resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz#4e89c9458acb61bc8fef19f4529973b2392839ee"
|
||||||
integrity sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==
|
integrity sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==
|
||||||
|
|
||||||
|
commander@7, commander@^7.2.0:
|
||||||
|
version "7.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7"
|
||||||
|
integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==
|
||||||
|
|
||||||
commander@^10.0.0:
|
commander@^10.0.0:
|
||||||
version "10.0.0"
|
version "10.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.0.tgz#71797971162cd3cf65f0b9d24eb28f8d303acdf1"
|
resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.0.tgz#71797971162cd3cf65f0b9d24eb28f8d303acdf1"
|
||||||
integrity sha512-zS5PnTI22FIRM6ylNW8G4Ap0IEOyk62fhLSD0+uHRT9McRCLGpkVNvao4bjimpK/GShynyQkFFxHhwMcETmduA==
|
integrity sha512-zS5PnTI22FIRM6ylNW8G4Ap0IEOyk62fhLSD0+uHRT9McRCLGpkVNvao4bjimpK/GShynyQkFFxHhwMcETmduA==
|
||||||
|
|
||||||
commander@^7.2.0:
|
|
||||||
version "7.2.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7"
|
|
||||||
integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==
|
|
||||||
|
|
||||||
commander@^8.0.0:
|
commander@^8.0.0:
|
||||||
version "8.3.0"
|
version "8.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66"
|
resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66"
|
||||||
@@ -1903,6 +1924,20 @@ core-js-compat@^3.25.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
browserslist "^4.21.5"
|
browserslist "^4.21.5"
|
||||||
|
|
||||||
|
cose-base@^1.0.0:
|
||||||
|
version "1.0.3"
|
||||||
|
resolved "https://registry.npmmirror.com/cose-base/-/cose-base-1.0.3.tgz#650334b41b869578a543358b80cda7e0abe0a60a"
|
||||||
|
integrity sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==
|
||||||
|
dependencies:
|
||||||
|
layout-base "^1.0.0"
|
||||||
|
|
||||||
|
cose-base@^2.2.0:
|
||||||
|
version "2.2.0"
|
||||||
|
resolved "https://registry.npmmirror.com/cose-base/-/cose-base-2.2.0.tgz#1c395c35b6e10bb83f9769ca8b817d614add5c01"
|
||||||
|
integrity sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==
|
||||||
|
dependencies:
|
||||||
|
layout-base "^2.0.0"
|
||||||
|
|
||||||
cosmiconfig@^7.0.1:
|
cosmiconfig@^7.0.1:
|
||||||
version "7.1.0"
|
version "7.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.1.0.tgz#1443b9afa596b670082ea46cbd8f6a62b84635f6"
|
resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.1.0.tgz#1443b9afa596b670082ea46cbd8f6a62b84635f6"
|
||||||
@@ -1973,6 +2008,280 @@ csstype@^3.0.2:
|
|||||||
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.1.tgz#841b532c45c758ee546a11d5bd7b7b473c8c30b9"
|
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.1.tgz#841b532c45c758ee546a11d5bd7b7b473c8c30b9"
|
||||||
integrity sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==
|
integrity sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==
|
||||||
|
|
||||||
|
cytoscape-cose-bilkent@^4.1.0:
|
||||||
|
version "4.1.0"
|
||||||
|
resolved "https://registry.npmmirror.com/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz#762fa121df9930ffeb51a495d87917c570ac209b"
|
||||||
|
integrity sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==
|
||||||
|
dependencies:
|
||||||
|
cose-base "^1.0.0"
|
||||||
|
|
||||||
|
cytoscape-fcose@^2.1.0:
|
||||||
|
version "2.2.0"
|
||||||
|
resolved "https://registry.npmmirror.com/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz#e4d6f6490df4fab58ae9cea9e5c3ab8d7472f471"
|
||||||
|
integrity sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==
|
||||||
|
dependencies:
|
||||||
|
cose-base "^2.2.0"
|
||||||
|
|
||||||
|
cytoscape@^3.23.0:
|
||||||
|
version "3.24.0"
|
||||||
|
resolved "https://registry.npmmirror.com/cytoscape/-/cytoscape-3.24.0.tgz#764e4ca3df37160b1c55244c648afd303a07e109"
|
||||||
|
integrity sha512-W9fJMrAfr/zKFzDCpRR/wn6uoEQ7gfbJmxPK5DadXj69XyAhZYi1QXLOE+UXJfXVXxqGM1o1eeiIrtxrtB43zA==
|
||||||
|
dependencies:
|
||||||
|
heap "^0.2.6"
|
||||||
|
lodash "^4.17.21"
|
||||||
|
|
||||||
|
"d3-array@2 - 3", "d3-array@2.10.0 - 3", "d3-array@2.5.0 - 3", d3-array@3, d3-array@^3.2.0:
|
||||||
|
version "3.2.3"
|
||||||
|
resolved "https://registry.npmmirror.com/d3-array/-/d3-array-3.2.3.tgz#39f1f4954e4a09ff69ac597c2d61906b04e84740"
|
||||||
|
integrity sha512-JRHwbQQ84XuAESWhvIPaUV4/1UYTBOLiOPGWqgFDHZS1D5QN9c57FbH3QpEnQMYiOXNzKUQyGTZf+EVO7RT5TQ==
|
||||||
|
dependencies:
|
||||||
|
internmap "1 - 2"
|
||||||
|
|
||||||
|
d3-axis@3:
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "https://registry.npmmirror.com/d3-axis/-/d3-axis-3.0.0.tgz#c42a4a13e8131d637b745fc2973824cfeaf93322"
|
||||||
|
integrity sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==
|
||||||
|
|
||||||
|
d3-brush@3:
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "https://registry.npmmirror.com/d3-brush/-/d3-brush-3.0.0.tgz#6f767c4ed8dcb79de7ede3e1c0f89e63ef64d31c"
|
||||||
|
integrity sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==
|
||||||
|
dependencies:
|
||||||
|
d3-dispatch "1 - 3"
|
||||||
|
d3-drag "2 - 3"
|
||||||
|
d3-interpolate "1 - 3"
|
||||||
|
d3-selection "3"
|
||||||
|
d3-transition "3"
|
||||||
|
|
||||||
|
d3-chord@3:
|
||||||
|
version "3.0.1"
|
||||||
|
resolved "https://registry.npmmirror.com/d3-chord/-/d3-chord-3.0.1.tgz#d156d61f485fce8327e6abf339cb41d8cbba6966"
|
||||||
|
integrity sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==
|
||||||
|
dependencies:
|
||||||
|
d3-path "1 - 3"
|
||||||
|
|
||||||
|
"d3-color@1 - 3", d3-color@3:
|
||||||
|
version "3.1.0"
|
||||||
|
resolved "https://registry.npmmirror.com/d3-color/-/d3-color-3.1.0.tgz#395b2833dfac71507f12ac2f7af23bf819de24e2"
|
||||||
|
integrity sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==
|
||||||
|
|
||||||
|
d3-contour@4:
|
||||||
|
version "4.0.2"
|
||||||
|
resolved "https://registry.npmmirror.com/d3-contour/-/d3-contour-4.0.2.tgz#bb92063bc8c5663acb2422f99c73cbb6c6ae3bcc"
|
||||||
|
integrity sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==
|
||||||
|
dependencies:
|
||||||
|
d3-array "^3.2.0"
|
||||||
|
|
||||||
|
d3-delaunay@6:
|
||||||
|
version "6.0.4"
|
||||||
|
resolved "https://registry.npmmirror.com/d3-delaunay/-/d3-delaunay-6.0.4.tgz#98169038733a0a5babbeda55054f795bb9e4a58b"
|
||||||
|
integrity sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==
|
||||||
|
dependencies:
|
||||||
|
delaunator "5"
|
||||||
|
|
||||||
|
"d3-dispatch@1 - 3", d3-dispatch@3:
|
||||||
|
version "3.0.1"
|
||||||
|
resolved "https://registry.npmmirror.com/d3-dispatch/-/d3-dispatch-3.0.1.tgz#5fc75284e9c2375c36c839411a0cf550cbfc4d5e"
|
||||||
|
integrity sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==
|
||||||
|
|
||||||
|
"d3-drag@2 - 3", d3-drag@3:
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "https://registry.npmmirror.com/d3-drag/-/d3-drag-3.0.0.tgz#994aae9cd23c719f53b5e10e3a0a6108c69607ba"
|
||||||
|
integrity sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==
|
||||||
|
dependencies:
|
||||||
|
d3-dispatch "1 - 3"
|
||||||
|
d3-selection "3"
|
||||||
|
|
||||||
|
"d3-dsv@1 - 3", d3-dsv@3:
|
||||||
|
version "3.0.1"
|
||||||
|
resolved "https://registry.npmmirror.com/d3-dsv/-/d3-dsv-3.0.1.tgz#c63af978f4d6a0d084a52a673922be2160789b73"
|
||||||
|
integrity sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==
|
||||||
|
dependencies:
|
||||||
|
commander "7"
|
||||||
|
iconv-lite "0.6"
|
||||||
|
rw "1"
|
||||||
|
|
||||||
|
"d3-ease@1 - 3", d3-ease@3:
|
||||||
|
version "3.0.1"
|
||||||
|
resolved "https://registry.npmmirror.com/d3-ease/-/d3-ease-3.0.1.tgz#9658ac38a2140d59d346160f1f6c30fda0bd12f4"
|
||||||
|
integrity sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==
|
||||||
|
|
||||||
|
d3-fetch@3:
|
||||||
|
version "3.0.1"
|
||||||
|
resolved "https://registry.npmmirror.com/d3-fetch/-/d3-fetch-3.0.1.tgz#83141bff9856a0edb5e38de89cdcfe63d0a60a22"
|
||||||
|
integrity sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==
|
||||||
|
dependencies:
|
||||||
|
d3-dsv "1 - 3"
|
||||||
|
|
||||||
|
d3-force@3:
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "https://registry.npmmirror.com/d3-force/-/d3-force-3.0.0.tgz#3e2ba1a61e70888fe3d9194e30d6d14eece155c4"
|
||||||
|
integrity sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==
|
||||||
|
dependencies:
|
||||||
|
d3-dispatch "1 - 3"
|
||||||
|
d3-quadtree "1 - 3"
|
||||||
|
d3-timer "1 - 3"
|
||||||
|
|
||||||
|
"d3-format@1 - 3", d3-format@3:
|
||||||
|
version "3.1.0"
|
||||||
|
resolved "https://registry.npmmirror.com/d3-format/-/d3-format-3.1.0.tgz#9260e23a28ea5cb109e93b21a06e24e2ebd55641"
|
||||||
|
integrity sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==
|
||||||
|
|
||||||
|
d3-geo@3:
|
||||||
|
version "3.1.0"
|
||||||
|
resolved "https://registry.npmmirror.com/d3-geo/-/d3-geo-3.1.0.tgz#74fd54e1f4cebd5185ac2039217a98d39b0a4c0e"
|
||||||
|
integrity sha512-JEo5HxXDdDYXCaWdwLRt79y7giK8SbhZJbFWXqbRTolCHFI5jRqteLzCsq51NKbUoX0PjBVSohxrx+NoOUujYA==
|
||||||
|
dependencies:
|
||||||
|
d3-array "2.5.0 - 3"
|
||||||
|
|
||||||
|
d3-hierarchy@3:
|
||||||
|
version "3.1.2"
|
||||||
|
resolved "https://registry.npmmirror.com/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz#b01cd42c1eed3d46db77a5966cf726f8c09160c6"
|
||||||
|
integrity sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==
|
||||||
|
|
||||||
|
"d3-interpolate@1 - 3", "d3-interpolate@1.2.0 - 3", d3-interpolate@3:
|
||||||
|
version "3.0.1"
|
||||||
|
resolved "https://registry.npmmirror.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz#3c47aa5b32c5b3dfb56ef3fd4342078a632b400d"
|
||||||
|
integrity sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==
|
||||||
|
dependencies:
|
||||||
|
d3-color "1 - 3"
|
||||||
|
|
||||||
|
"d3-path@1 - 3", d3-path@3, d3-path@^3.1.0:
|
||||||
|
version "3.1.0"
|
||||||
|
resolved "https://registry.npmmirror.com/d3-path/-/d3-path-3.1.0.tgz#22df939032fb5a71ae8b1800d61ddb7851c42526"
|
||||||
|
integrity sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==
|
||||||
|
|
||||||
|
d3-polygon@3:
|
||||||
|
version "3.0.1"
|
||||||
|
resolved "https://registry.npmmirror.com/d3-polygon/-/d3-polygon-3.0.1.tgz#0b45d3dd1c48a29c8e057e6135693ec80bf16398"
|
||||||
|
integrity sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==
|
||||||
|
|
||||||
|
"d3-quadtree@1 - 3", d3-quadtree@3:
|
||||||
|
version "3.0.1"
|
||||||
|
resolved "https://registry.npmmirror.com/d3-quadtree/-/d3-quadtree-3.0.1.tgz#6dca3e8be2b393c9a9d514dabbd80a92deef1a4f"
|
||||||
|
integrity sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==
|
||||||
|
|
||||||
|
d3-random@3:
|
||||||
|
version "3.0.1"
|
||||||
|
resolved "https://registry.npmmirror.com/d3-random/-/d3-random-3.0.1.tgz#d4926378d333d9c0bfd1e6fa0194d30aebaa20f4"
|
||||||
|
integrity sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==
|
||||||
|
|
||||||
|
d3-scale-chromatic@3:
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "https://registry.npmmirror.com/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz#15b4ceb8ca2bb0dcb6d1a641ee03d59c3b62376a"
|
||||||
|
integrity sha512-Lx9thtxAKrO2Pq6OO2Ua474opeziKr279P/TKZsMAhYyNDD3EnCffdbgeSYN5O7m2ByQsxtuP2CSDczNUIZ22g==
|
||||||
|
dependencies:
|
||||||
|
d3-color "1 - 3"
|
||||||
|
d3-interpolate "1 - 3"
|
||||||
|
|
||||||
|
d3-scale@4:
|
||||||
|
version "4.0.2"
|
||||||
|
resolved "https://registry.npmmirror.com/d3-scale/-/d3-scale-4.0.2.tgz#82b38e8e8ff7080764f8dcec77bd4be393689396"
|
||||||
|
integrity sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==
|
||||||
|
dependencies:
|
||||||
|
d3-array "2.10.0 - 3"
|
||||||
|
d3-format "1 - 3"
|
||||||
|
d3-interpolate "1.2.0 - 3"
|
||||||
|
d3-time "2.1.1 - 3"
|
||||||
|
d3-time-format "2 - 4"
|
||||||
|
|
||||||
|
"d3-selection@2 - 3", d3-selection@3:
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "https://registry.npmmirror.com/d3-selection/-/d3-selection-3.0.0.tgz#c25338207efa72cc5b9bd1458a1a41901f1e1b31"
|
||||||
|
integrity sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==
|
||||||
|
|
||||||
|
d3-shape@3:
|
||||||
|
version "3.2.0"
|
||||||
|
resolved "https://registry.npmmirror.com/d3-shape/-/d3-shape-3.2.0.tgz#a1a839cbd9ba45f28674c69d7f855bcf91dfc6a5"
|
||||||
|
integrity sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==
|
||||||
|
dependencies:
|
||||||
|
d3-path "^3.1.0"
|
||||||
|
|
||||||
|
"d3-time-format@2 - 4", d3-time-format@4:
|
||||||
|
version "4.1.0"
|
||||||
|
resolved "https://registry.npmmirror.com/d3-time-format/-/d3-time-format-4.1.0.tgz#7ab5257a5041d11ecb4fe70a5c7d16a195bb408a"
|
||||||
|
integrity sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==
|
||||||
|
dependencies:
|
||||||
|
d3-time "1 - 3"
|
||||||
|
|
||||||
|
"d3-time@1 - 3", "d3-time@2.1.1 - 3", d3-time@3:
|
||||||
|
version "3.1.0"
|
||||||
|
resolved "https://registry.npmmirror.com/d3-time/-/d3-time-3.1.0.tgz#9310db56e992e3c0175e1ef385e545e48a9bb5c7"
|
||||||
|
integrity sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==
|
||||||
|
dependencies:
|
||||||
|
d3-array "2 - 3"
|
||||||
|
|
||||||
|
"d3-timer@1 - 3", d3-timer@3:
|
||||||
|
version "3.0.1"
|
||||||
|
resolved "https://registry.npmmirror.com/d3-timer/-/d3-timer-3.0.1.tgz#6284d2a2708285b1abb7e201eda4380af35e63b0"
|
||||||
|
integrity sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==
|
||||||
|
|
||||||
|
"d3-transition@2 - 3", d3-transition@3:
|
||||||
|
version "3.0.1"
|
||||||
|
resolved "https://registry.npmmirror.com/d3-transition/-/d3-transition-3.0.1.tgz#6869fdde1448868077fdd5989200cb61b2a1645f"
|
||||||
|
integrity sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==
|
||||||
|
dependencies:
|
||||||
|
d3-color "1 - 3"
|
||||||
|
d3-dispatch "1 - 3"
|
||||||
|
d3-ease "1 - 3"
|
||||||
|
d3-interpolate "1 - 3"
|
||||||
|
d3-timer "1 - 3"
|
||||||
|
|
||||||
|
d3-zoom@3:
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "https://registry.npmmirror.com/d3-zoom/-/d3-zoom-3.0.0.tgz#d13f4165c73217ffeaa54295cd6969b3e7aee8f3"
|
||||||
|
integrity sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==
|
||||||
|
dependencies:
|
||||||
|
d3-dispatch "1 - 3"
|
||||||
|
d3-drag "2 - 3"
|
||||||
|
d3-interpolate "1 - 3"
|
||||||
|
d3-selection "2 - 3"
|
||||||
|
d3-transition "2 - 3"
|
||||||
|
|
||||||
|
d3@^7.4.0, d3@^7.8.2:
|
||||||
|
version "7.8.4"
|
||||||
|
resolved "https://registry.npmmirror.com/d3/-/d3-7.8.4.tgz#e35d45800e4068cab07e59e5d883a4bb42ab217f"
|
||||||
|
integrity sha512-q2WHStdhiBtD8DMmhDPyJmXUxr6VWRngKyiJ5EfXMxPw+tqT6BhNjhJZ4w3BHsNm3QoVfZLY8Orq/qPFczwKRA==
|
||||||
|
dependencies:
|
||||||
|
d3-array "3"
|
||||||
|
d3-axis "3"
|
||||||
|
d3-brush "3"
|
||||||
|
d3-chord "3"
|
||||||
|
d3-color "3"
|
||||||
|
d3-contour "4"
|
||||||
|
d3-delaunay "6"
|
||||||
|
d3-dispatch "3"
|
||||||
|
d3-drag "3"
|
||||||
|
d3-dsv "3"
|
||||||
|
d3-ease "3"
|
||||||
|
d3-fetch "3"
|
||||||
|
d3-force "3"
|
||||||
|
d3-format "3"
|
||||||
|
d3-geo "3"
|
||||||
|
d3-hierarchy "3"
|
||||||
|
d3-interpolate "3"
|
||||||
|
d3-path "3"
|
||||||
|
d3-polygon "3"
|
||||||
|
d3-quadtree "3"
|
||||||
|
d3-random "3"
|
||||||
|
d3-scale "4"
|
||||||
|
d3-scale-chromatic "3"
|
||||||
|
d3-selection "3"
|
||||||
|
d3-shape "3"
|
||||||
|
d3-time "3"
|
||||||
|
d3-time-format "4"
|
||||||
|
d3-timer "3"
|
||||||
|
d3-transition "3"
|
||||||
|
d3-zoom "3"
|
||||||
|
|
||||||
|
dagre-d3-es@7.0.10:
|
||||||
|
version "7.0.10"
|
||||||
|
resolved "https://registry.npmmirror.com/dagre-d3-es/-/dagre-d3-es-7.0.10.tgz#19800d4be674379a3cd8c86a8216a2ac6827cadc"
|
||||||
|
integrity sha512-qTCQmEhcynucuaZgY5/+ti3X/rnszKZhEQH/ZdWdtP1tA/y3VoHJzcVrO9pjjJCNpigfscAtoUB5ONcd2wNn0A==
|
||||||
|
dependencies:
|
||||||
|
d3 "^7.8.2"
|
||||||
|
lodash-es "^4.17.21"
|
||||||
|
|
||||||
damerau-levenshtein@^1.0.8:
|
damerau-levenshtein@^1.0.8:
|
||||||
version "1.0.8"
|
version "1.0.8"
|
||||||
resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz#b43d286ccbd36bc5b2f7ed41caf2d0aba1f8a6e7"
|
resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz#b43d286ccbd36bc5b2f7ed41caf2d0aba1f8a6e7"
|
||||||
@@ -1983,6 +2292,11 @@ data-uri-to-buffer@^4.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz#d8feb2b2881e6a4f58c2e08acfd0e2834e26222e"
|
resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz#d8feb2b2881e6a4f58c2e08acfd0e2834e26222e"
|
||||||
integrity sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==
|
integrity sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==
|
||||||
|
|
||||||
|
dayjs@^1.11.7:
|
||||||
|
version "1.11.7"
|
||||||
|
resolved "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.7.tgz#4b296922642f70999544d1144a2c25730fce63e2"
|
||||||
|
integrity sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ==
|
||||||
|
|
||||||
debug@^3.2.7:
|
debug@^3.2.7:
|
||||||
version "3.2.7"
|
version "3.2.7"
|
||||||
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a"
|
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a"
|
||||||
@@ -2050,6 +2364,13 @@ define-properties@^1.1.3, define-properties@^1.1.4:
|
|||||||
has-property-descriptors "^1.0.0"
|
has-property-descriptors "^1.0.0"
|
||||||
object-keys "^1.1.1"
|
object-keys "^1.1.1"
|
||||||
|
|
||||||
|
delaunator@5:
|
||||||
|
version "5.0.0"
|
||||||
|
resolved "https://registry.npmmirror.com/delaunator/-/delaunator-5.0.0.tgz#60f052b28bd91c9b4566850ebf7756efe821d81b"
|
||||||
|
integrity sha512-AyLvtyJdbv/U1GkiS6gUUzclRoAY4Gs75qkMygJJhU75LW4DNuSF2RMzpxs9jw9Oz1BobHjTdkG3zdP55VxAqw==
|
||||||
|
dependencies:
|
||||||
|
robust-predicates "^3.0.0"
|
||||||
|
|
||||||
delayed-stream@~1.0.0:
|
delayed-stream@~1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
|
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
|
||||||
@@ -2107,6 +2428,11 @@ domhandler@^4.2.0, domhandler@^4.3.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
domelementtype "^2.2.0"
|
domelementtype "^2.2.0"
|
||||||
|
|
||||||
|
dompurify@2.4.5:
|
||||||
|
version "2.4.5"
|
||||||
|
resolved "https://registry.npmmirror.com/dompurify/-/dompurify-2.4.5.tgz#0e89a27601f0bad978f9a924e7a05d5d2cccdd87"
|
||||||
|
integrity sha512-jggCCd+8Iqp4Tsz0nIvpcb22InKEBrGz5dw3EQJMs8HPJDsKbFIO3STYtAvCfDx26Muevn1MHVI0XxjgFfmiSA==
|
||||||
|
|
||||||
domutils@^2.8.0:
|
domutils@^2.8.0:
|
||||||
version "2.8.0"
|
version "2.8.0"
|
||||||
resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135"
|
resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135"
|
||||||
@@ -2126,6 +2452,11 @@ electron-to-chromium@^1.4.284:
|
|||||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.345.tgz#c90b7183b39245cddf0e990337469063bfced6f0"
|
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.345.tgz#c90b7183b39245cddf0e990337469063bfced6f0"
|
||||||
integrity sha512-znGhOQK2TUYLICgS25uaM0a7pHy66rSxbre7l762vg9AUoCcJK+Bu+HCPWpjL/U/kK8/Hf+6E0szAUJSyVYb3Q==
|
integrity sha512-znGhOQK2TUYLICgS25uaM0a7pHy66rSxbre7l762vg9AUoCcJK+Bu+HCPWpjL/U/kK8/Hf+6E0szAUJSyVYb3Q==
|
||||||
|
|
||||||
|
elkjs@^0.8.2:
|
||||||
|
version "0.8.2"
|
||||||
|
resolved "https://registry.npmmirror.com/elkjs/-/elkjs-0.8.2.tgz#c37763c5a3e24e042e318455e0147c912a7c248e"
|
||||||
|
integrity sha512-L6uRgvZTH+4OF5NE/MBbzQx/WYpru1xCBE9respNj6qznEewGUIfhzmm7horWWxbNO2M0WckQypGctR8lH79xQ==
|
||||||
|
|
||||||
emoji-picker-react@^4.4.7:
|
emoji-picker-react@^4.4.7:
|
||||||
version "4.4.8"
|
version "4.4.8"
|
||||||
resolved "https://registry.yarnpkg.com/emoji-picker-react/-/emoji-picker-react-4.4.8.tgz#cd18e942720d0d01e3d488a008f5e79aa315ec87"
|
resolved "https://registry.yarnpkg.com/emoji-picker-react/-/emoji-picker-react-4.4.8.tgz#cd18e942720d0d01e3d488a008f5e79aa315ec87"
|
||||||
@@ -2905,6 +3236,11 @@ hastscript@^7.0.0:
|
|||||||
property-information "^6.0.0"
|
property-information "^6.0.0"
|
||||||
space-separated-tokens "^2.0.0"
|
space-separated-tokens "^2.0.0"
|
||||||
|
|
||||||
|
heap@^0.2.6:
|
||||||
|
version "0.2.7"
|
||||||
|
resolved "https://registry.npmmirror.com/heap/-/heap-0.2.7.tgz#1e6adf711d3f27ce35a81fe3b7bd576c2260a8fc"
|
||||||
|
integrity sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg==
|
||||||
|
|
||||||
highlight.js@~11.7.0:
|
highlight.js@~11.7.0:
|
||||||
version "11.7.0"
|
version "11.7.0"
|
||||||
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.7.0.tgz#3ff0165bc843f8c9bce1fd89e2fda9143d24b11e"
|
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.7.0.tgz#3ff0165bc843f8c9bce1fd89e2fda9143d24b11e"
|
||||||
@@ -2927,6 +3263,13 @@ husky@^8.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/husky/-/husky-8.0.3.tgz#4936d7212e46d1dea28fef29bb3a108872cd9184"
|
resolved "https://registry.yarnpkg.com/husky/-/husky-8.0.3.tgz#4936d7212e46d1dea28fef29bb3a108872cd9184"
|
||||||
integrity sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==
|
integrity sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==
|
||||||
|
|
||||||
|
iconv-lite@0.6:
|
||||||
|
version "0.6.3"
|
||||||
|
resolved "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501"
|
||||||
|
integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==
|
||||||
|
dependencies:
|
||||||
|
safer-buffer ">= 2.1.2 < 3.0.0"
|
||||||
|
|
||||||
ignore@^5.2.0:
|
ignore@^5.2.0:
|
||||||
version "5.2.4"
|
version "5.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324"
|
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324"
|
||||||
@@ -2982,6 +3325,11 @@ internal-slot@^1.0.3, internal-slot@^1.0.4, internal-slot@^1.0.5:
|
|||||||
has "^1.0.3"
|
has "^1.0.3"
|
||||||
side-channel "^1.0.4"
|
side-channel "^1.0.4"
|
||||||
|
|
||||||
|
"internmap@1 - 2":
|
||||||
|
version "2.0.3"
|
||||||
|
resolved "https://registry.npmmirror.com/internmap/-/internmap-2.0.3.tgz#6685f23755e43c524e251d29cbc97248e3061009"
|
||||||
|
integrity sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==
|
||||||
|
|
||||||
is-arguments@^1.1.1:
|
is-arguments@^1.1.1:
|
||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b"
|
resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b"
|
||||||
@@ -3272,6 +3620,11 @@ katex@^0.15.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
commander "^8.0.0"
|
commander "^8.0.0"
|
||||||
|
|
||||||
|
khroma@^2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.npmmirror.com/khroma/-/khroma-2.0.0.tgz#7577de98aed9f36c7a474c4d453d94c0d6c6588b"
|
||||||
|
integrity sha512-2J8rDNlQWbtiNYThZRvmMv5yt44ZakX+Tz5ZIp/mN1pt4snn+m030Va5Z4v8xA0cQFDXBwO/8i42xL4QPsVk3g==
|
||||||
|
|
||||||
kleur@^4.0.3:
|
kleur@^4.0.3:
|
||||||
version "4.1.5"
|
version "4.1.5"
|
||||||
resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.5.tgz#95106101795f7050c6c650f350c683febddb1780"
|
resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.5.tgz#95106101795f7050c6c650f350c683febddb1780"
|
||||||
@@ -3289,6 +3642,16 @@ language-tags@=1.0.5:
|
|||||||
dependencies:
|
dependencies:
|
||||||
language-subtag-registry "~0.3.2"
|
language-subtag-registry "~0.3.2"
|
||||||
|
|
||||||
|
layout-base@^1.0.0:
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.npmmirror.com/layout-base/-/layout-base-1.0.2.tgz#1291e296883c322a9dd4c5dd82063721b53e26e2"
|
||||||
|
integrity sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==
|
||||||
|
|
||||||
|
layout-base@^2.0.0:
|
||||||
|
version "2.0.1"
|
||||||
|
resolved "https://registry.npmmirror.com/layout-base/-/layout-base-2.0.1.tgz#d0337913586c90f9c2c075292069f5c2da5dd285"
|
||||||
|
integrity sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==
|
||||||
|
|
||||||
levn@^0.4.1:
|
levn@^0.4.1:
|
||||||
version "0.4.1"
|
version "0.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade"
|
resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade"
|
||||||
@@ -3347,6 +3710,11 @@ locate-path@^6.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
p-locate "^5.0.0"
|
p-locate "^5.0.0"
|
||||||
|
|
||||||
|
lodash-es@^4.17.21:
|
||||||
|
version "4.17.21"
|
||||||
|
resolved "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee"
|
||||||
|
integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==
|
||||||
|
|
||||||
lodash.debounce@^4.0.8:
|
lodash.debounce@^4.0.8:
|
||||||
version "4.0.8"
|
version "4.0.8"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
|
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
|
||||||
@@ -3357,6 +3725,11 @@ lodash.merge@^4.6.2:
|
|||||||
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
|
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
|
||||||
integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
|
integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
|
||||||
|
|
||||||
|
lodash@^4.17.21:
|
||||||
|
version "4.17.21"
|
||||||
|
resolved "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
||||||
|
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
||||||
|
|
||||||
log-update@^4.0.0:
|
log-update@^4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/log-update/-/log-update-4.0.0.tgz#589ecd352471f2a1c0c570287543a64dfd20e0a1"
|
resolved "https://registry.yarnpkg.com/log-update/-/log-update-4.0.0.tgz#589ecd352471f2a1c0c570287543a64dfd20e0a1"
|
||||||
@@ -3574,6 +3947,29 @@ merge2@^1.3.0, merge2@^1.4.1:
|
|||||||
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
|
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
|
||||||
integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
|
integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
|
||||||
|
|
||||||
|
mermaid@^10.1.0:
|
||||||
|
version "10.1.0"
|
||||||
|
resolved "https://registry.npmmirror.com/mermaid/-/mermaid-10.1.0.tgz#6e40d5250174f4750ca6548e4ee00f6ae210855a"
|
||||||
|
integrity sha512-LYekSMNJygI1VnMizAPUddY95hZxOjwZxr7pODczILInO0dhQKuhXeu4sargtnuTwCilSuLS7Uiq/Qn7HTVrmA==
|
||||||
|
dependencies:
|
||||||
|
"@braintree/sanitize-url" "^6.0.0"
|
||||||
|
"@khanacademy/simple-markdown" "^0.8.6"
|
||||||
|
cytoscape "^3.23.0"
|
||||||
|
cytoscape-cose-bilkent "^4.1.0"
|
||||||
|
cytoscape-fcose "^2.1.0"
|
||||||
|
d3 "^7.4.0"
|
||||||
|
dagre-d3-es "7.0.10"
|
||||||
|
dayjs "^1.11.7"
|
||||||
|
dompurify "2.4.5"
|
||||||
|
elkjs "^0.8.2"
|
||||||
|
khroma "^2.0.0"
|
||||||
|
lodash-es "^4.17.21"
|
||||||
|
non-layered-tidy-tree-layout "^2.0.2"
|
||||||
|
stylis "^4.1.2"
|
||||||
|
ts-dedent "^2.2.0"
|
||||||
|
uuid "^9.0.0"
|
||||||
|
web-worker "^1.2.0"
|
||||||
|
|
||||||
micromark-core-commonmark@^1.0.0, micromark-core-commonmark@^1.0.1:
|
micromark-core-commonmark@^1.0.0, micromark-core-commonmark@^1.0.1:
|
||||||
version "1.0.6"
|
version "1.0.6"
|
||||||
resolved "https://registry.yarnpkg.com/micromark-core-commonmark/-/micromark-core-commonmark-1.0.6.tgz#edff4c72e5993d93724a3c206970f5a15b0585ad"
|
resolved "https://registry.yarnpkg.com/micromark-core-commonmark/-/micromark-core-commonmark-1.0.6.tgz#edff4c72e5993d93724a3c206970f5a15b0585ad"
|
||||||
@@ -3970,6 +4366,11 @@ node-releases@^2.0.8:
|
|||||||
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.10.tgz#c311ebae3b6a148c89b1813fd7c4d3c024ef537f"
|
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.10.tgz#c311ebae3b6a148c89b1813fd7c4d3c024ef537f"
|
||||||
integrity sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==
|
integrity sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==
|
||||||
|
|
||||||
|
non-layered-tidy-tree-layout@^2.0.2:
|
||||||
|
version "2.0.2"
|
||||||
|
resolved "https://registry.npmmirror.com/non-layered-tidy-tree-layout/-/non-layered-tidy-tree-layout-2.0.2.tgz#57d35d13c356643fc296a55fb11ac15e74da7804"
|
||||||
|
integrity sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw==
|
||||||
|
|
||||||
normalize-path@^3.0.0, normalize-path@~3.0.0:
|
normalize-path@^3.0.0, normalize-path@~3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
|
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
|
||||||
@@ -4520,6 +4921,11 @@ rimraf@^3.0.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
glob "^7.1.3"
|
glob "^7.1.3"
|
||||||
|
|
||||||
|
robust-predicates@^3.0.0:
|
||||||
|
version "3.0.1"
|
||||||
|
resolved "https://registry.npmmirror.com/robust-predicates/-/robust-predicates-3.0.1.tgz#ecde075044f7f30118682bd9fb3f123109577f9a"
|
||||||
|
integrity sha512-ndEIpszUHiG4HtDsQLeIuMvRsDnn8c8rYStabochtUeCvfuvNptb5TUbVD68LRAILPX7p9nqQGh4xJgn3EHS/g==
|
||||||
|
|
||||||
run-parallel@^1.1.9:
|
run-parallel@^1.1.9:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee"
|
resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee"
|
||||||
@@ -4527,6 +4933,11 @@ run-parallel@^1.1.9:
|
|||||||
dependencies:
|
dependencies:
|
||||||
queue-microtask "^1.2.2"
|
queue-microtask "^1.2.2"
|
||||||
|
|
||||||
|
rw@1:
|
||||||
|
version "1.3.3"
|
||||||
|
resolved "https://registry.npmmirror.com/rw/-/rw-1.3.3.tgz#3f862dfa91ab766b14885ef4d01124bfda074fb4"
|
||||||
|
integrity sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==
|
||||||
|
|
||||||
rxjs@^7.8.0:
|
rxjs@^7.8.0:
|
||||||
version "7.8.0"
|
version "7.8.0"
|
||||||
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.0.tgz#90a938862a82888ff4c7359811a595e14e1e09a4"
|
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.0.tgz#90a938862a82888ff4c7359811a595e14e1e09a4"
|
||||||
@@ -4550,6 +4961,11 @@ safe-regex-test@^1.0.0:
|
|||||||
get-intrinsic "^1.1.3"
|
get-intrinsic "^1.1.3"
|
||||||
is-regex "^1.1.4"
|
is-regex "^1.1.4"
|
||||||
|
|
||||||
|
"safer-buffer@>= 2.1.2 < 3.0.0":
|
||||||
|
version "2.1.2"
|
||||||
|
resolved "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
|
||||||
|
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
|
||||||
|
|
||||||
sass@^1.59.2:
|
sass@^1.59.2:
|
||||||
version "1.60.0"
|
version "1.60.0"
|
||||||
resolved "https://registry.yarnpkg.com/sass/-/sass-1.60.0.tgz#657f0c23a302ac494b09a5ba8497b739fb5b5a81"
|
resolved "https://registry.yarnpkg.com/sass/-/sass-1.60.0.tgz#657f0c23a302ac494b09a5ba8497b739fb5b5a81"
|
||||||
@@ -4784,6 +5200,11 @@ styled-jsx@5.1.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
client-only "0.0.1"
|
client-only "0.0.1"
|
||||||
|
|
||||||
|
stylis@^4.1.2:
|
||||||
|
version "4.1.4"
|
||||||
|
resolved "https://registry.npmmirror.com/stylis/-/stylis-4.1.4.tgz#9cb60e7153d8ac6d02d773552bf51c7a0344535b"
|
||||||
|
integrity sha512-USf5pszRYwuE6hg9by0OkKChkQYEXfkeTtm0xKw+jqQhwyjCVLdYyMBK7R+n7dhzsblAWJnGxju4vxq5eH20GQ==
|
||||||
|
|
||||||
supports-color@^5.3.0:
|
supports-color@^5.3.0:
|
||||||
version "5.5.0"
|
version "5.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
|
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
|
||||||
@@ -4879,6 +5300,11 @@ trough@^2.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/trough/-/trough-2.1.0.tgz#0f7b511a4fde65a46f18477ab38849b22c554876"
|
resolved "https://registry.yarnpkg.com/trough/-/trough-2.1.0.tgz#0f7b511a4fde65a46f18477ab38849b22c554876"
|
||||||
integrity sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==
|
integrity sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==
|
||||||
|
|
||||||
|
ts-dedent@^2.2.0:
|
||||||
|
version "2.2.0"
|
||||||
|
resolved "https://registry.npmmirror.com/ts-dedent/-/ts-dedent-2.2.0.tgz#39e4bd297cd036292ae2394eb3412be63f563bb5"
|
||||||
|
integrity sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==
|
||||||
|
|
||||||
tsconfig-paths@^3.14.1:
|
tsconfig-paths@^3.14.1:
|
||||||
version "3.14.2"
|
version "3.14.2"
|
||||||
resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz#6e32f1f79412decd261f92d633a9dc1cfa99f088"
|
resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz#6e32f1f79412decd261f92d633a9dc1cfa99f088"
|
||||||
@@ -5072,6 +5498,11 @@ use-sync-external-store@1.2.0, use-sync-external-store@^1.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a"
|
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a"
|
||||||
integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==
|
integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==
|
||||||
|
|
||||||
|
uuid@^9.0.0:
|
||||||
|
version "9.0.0"
|
||||||
|
resolved "https://registry.npmmirror.com/uuid/-/uuid-9.0.0.tgz#592f550650024a38ceb0c562f2f6aa435761efb5"
|
||||||
|
integrity sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==
|
||||||
|
|
||||||
uvu@^0.5.0:
|
uvu@^0.5.0:
|
||||||
version "0.5.6"
|
version "0.5.6"
|
||||||
resolved "https://registry.yarnpkg.com/uvu/-/uvu-0.5.6.tgz#2754ca20bcb0bb59b64e9985e84d2e81058502df"
|
resolved "https://registry.yarnpkg.com/uvu/-/uvu-0.5.6.tgz#2754ca20bcb0bb59b64e9985e84d2e81058502df"
|
||||||
@@ -5118,6 +5549,11 @@ web-streams-polyfill@^3.0.3:
|
|||||||
resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz#71c2718c52b45fd49dbeee88634b3a60ceab42a6"
|
resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz#71c2718c52b45fd49dbeee88634b3a60ceab42a6"
|
||||||
integrity sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==
|
integrity sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==
|
||||||
|
|
||||||
|
web-worker@^1.2.0:
|
||||||
|
version "1.2.0"
|
||||||
|
resolved "https://registry.npmmirror.com/web-worker/-/web-worker-1.2.0.tgz#5d85a04a7fbc1e7db58f66595d7a3ac7c9c180da"
|
||||||
|
integrity sha512-PgF341avzqyx60neE9DD+XS26MMNMoUQRz9NOZwW32nPQrF6p77f1htcnjBSEV8BGMKZ16choqUG4hyI0Hx7mA==
|
||||||
|
|
||||||
which-boxed-primitive@^1.0.2:
|
which-boxed-primitive@^1.0.2:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6"
|
resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6"
|
||||||
|
Reference in New Issue
Block a user