Compare commits

...

60 Commits

Author SHA1 Message Date
DeanYao
f271e1654a Merge pull request #4460 from chenyu-01/feat-multi-models
Feat [UI/UX Page] [Auth] Add input box for claude api on auth page
2024-04-10 09:15:33 +08:00
chenyu-01
f6d4ae6fc0 update Auth SubTips 2024-04-08 17:52:00 +08:00
brett
a902f1abac Merge branch 'ChatGPTNextWeb:feat-multi-models' into feat-multi-models 2024-04-08 17:36:52 +08:00
butterfly
63f9063255 feat: call claude api not in credential 'include' mode 2024-04-08 15:33:27 +08:00
butterfly
6dad353e1c feat: call claude api not in credential 'include' mode 2024-04-08 15:33:02 +08:00
brett
da0ca997c1 Merge branch 'ChatGPTNextWeb:feat-multi-models' into feat-multi-models 2024-04-08 14:16:27 +08:00
butterfly
5446d8d4a2 feat: fix illegal exports in app/api/anthropic/[...path]/route.ts 2024-04-08 13:59:55 +08:00
butterfly
ef7617d545 feat: configs about app client 2024-04-08 13:41:02 +08:00
chenyu-01
f871c67da7 feat: auth page setting for claude api
feat: fix lang support
2024-04-08 11:29:31 +08:00
butterfly
0fbb560e90 feat: delete returned models in modals function of ClaudeApi instance 2024-04-07 20:05:19 +08:00
butterfly
86b5c55855 feat: roles must alternate between user and assistant in claude, so add a fake assistant message between two user messages 2024-04-07 18:02:31 +08:00
butterfly
768decde93 feat: parse response message 2024-04-07 15:20:27 +08:00
butterfly
3cb4315193 feat: clean codes 2024-04-07 11:50:25 +08:00
butterfly
69b079c86e feat: dev done 2024-04-07 11:32:57 +08:00
butterfly
15e595837b feat: settings command dev done 2024-04-02 14:21:49 +08:00
DeanYao
e38b527ac2 Merge pull request #3205 from H0llyW00dzZ/summarizelogic
Refactor Summarize Logic
2024-03-28 15:19:32 +08:00
DeanYao
113d9612db Merge pull request #3280 from surkaa/patch-1
Update .env.template 更正单词
2024-03-28 13:40:17 +08:00
DeanYao
6b3daec23f Merge pull request #3314 from H0llyW00dzZ/text-moderation-azure
Feat ChatGPT LLM Api [Console Log] [Text Moderation] [Azure]
2024-03-28 13:38:56 +08:00
DeanYao
e056a1d46d Merge pull request #3405 from Yuliang-Lee/fix/MessageSelectorWarning
fix: MessageSelectorWarning
2024-03-28 11:38:45 +08:00
DeanYao
57026f6262 Merge pull request #3424 from H0llyW00dzZ/serverrside
Refactor Api Common [Server Side] [Console Log]
2024-03-28 11:29:48 +08:00
DeanYao
8ef77f50c3 Merge branch 'main' into serverrside 2024-03-28 11:20:52 +08:00
DeanYao
93e21515e5 Merge pull request #4408 from hmhuming/main
fix docker
2024-03-28 10:28:11 +08:00
hmhuming
24caa3b97b Merge branch 'main' of https://github.com/hmhuming/ChatGPT-Next-Web 2024-03-28 09:18:51 +08:00
DeanYao
c93b36fe79 Merge pull request #3508 from reece00/Mask-language
The language filtering option of the mask is stored
2024-03-27 19:58:30 +08:00
DeanYao
0de9242a26 Merge pull request #3529 from erich2s/chat-item-selected-border
fix(chat-item): selected ChatItem showing border in other pages
2024-03-27 19:00:16 +08:00
hmhuming
53fb52c6c0 fix docker 2024-03-27 17:58:55 +08:00
DeanYao
afaa529ba6 Merge pull request #3870 from Dup4/fix-webdav-check
fix: webdav check httpcode list
2024-03-27 14:04:15 +08:00
DeanYao
43824bd621 Merge pull request #4193 from MrrDrr/env_bug_fix
Update README.md
2024-03-26 17:53:57 +08:00
DeanYao
3c97a4f5a1 Merge pull request #4091 from H0llyW00dzZ/docker-ignore
Update Docker Ignore
2024-03-26 17:44:52 +08:00
DeanYao
711bf190d4 Merge pull request #4264 from ChatGPTNextWeb/dependabot/npm_and_yarn/tauri-apps/cli-1.5.11
chore(deps-dev): bump @tauri-apps/cli from 1.5.7 to 1.5.11
2024-03-26 17:32:01 +08:00
DeanYao
1049006cf9 Merge pull request #4366 from Essmatiko123/dependabot/npm_and_yarn/eslint-plugin-prettier-5.1.3
chore(deps-dev): bump eslint-plugin-prettier from 4.2.1 to 5.1.3
2024-03-26 13:57:50 +08:00
dependabot[bot]
76603d108d chore(deps-dev): bump @tauri-apps/cli from 1.5.7 to 1.5.11
Bumps [@tauri-apps/cli](https://github.com/tauri-apps/tauri) from 1.5.7 to 1.5.11.
- [Release notes](https://github.com/tauri-apps/tauri/releases)
- [Commits](https://github.com/tauri-apps/tauri/compare/@tauri-apps/cli-v1.5.7...@tauri-apps/cli-v1.5.11)

---
updated-dependencies:
- dependency-name: "@tauri-apps/cli"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-26 05:42:55 +00:00
DeanYao
5bc3930230 Merge pull request #4389 from ChatGPTNextWeb/dependabot/npm_and_yarn/types/react-18.2.70
chore(deps-dev): bump @types/react from 18.2.14 to 18.2.70
2024-03-26 13:41:13 +08:00
DeanYao
e5edd851b3 Merge pull request #4390 from ChatGPTNextWeb/dependabot/npm_and_yarn/emoji-picker-react-4.9.2
chore(deps): bump emoji-picker-react from 4.5.15 to 4.9.2
2024-03-26 13:35:03 +08:00
dependabot[bot]
dcad400758 chore(deps-dev): bump @types/react from 18.2.14 to 18.2.70
Bumps [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react) from 18.2.14 to 18.2.70.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react)

---
updated-dependencies:
- dependency-name: "@types/react"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-26 05:26:51 +00:00
DeanYao
a1aaea9c55 Merge pull request #4391 from ChatGPTNextWeb/dependabot/npm_and_yarn/types/node-20.11.30
chore(deps-dev): bump @types/node from 20.9.0 to 20.11.30
2024-03-26 13:24:24 +08:00
DeanYao
a4e4286e04 Merge pull request #4186 from MrrDrr/formula_rendering
support \(...\) and \[...\] style math formula
2024-03-25 19:55:57 +08:00
dependabot[bot]
6dd7a6a171 chore(deps-dev): bump @types/node from 20.9.0 to 20.11.30
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 20.9.0 to 20.11.30.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-25 10:47:55 +00:00
dependabot[bot]
8e554a87b0 chore(deps): bump emoji-picker-react from 4.5.15 to 4.9.2
Bumps [emoji-picker-react](https://github.com/ealush/emoji-picker-react) from 4.5.15 to 4.9.2.
- [Release notes](https://github.com/ealush/emoji-picker-react/releases)
- [Commits](https://github.com/ealush/emoji-picker-react/commits)

---
updated-dependencies:
- dependency-name: emoji-picker-react
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-25 10:46:24 +00:00
fred-bf
f1b4c083a4 Merge pull request #4379 from EasonQwQ/main
Fix: Handle empty server response in API call
2024-03-24 14:18:14 +08:00
fred-bf
90af4e3b77 Merge pull request #4381 from ChatGPTNextWeb/fred-bf-patch-3
patch: disable webdav redirect
2024-03-24 14:17:11 +08:00
fred-bf
e8d76a513d patch: disable webdav redirect 2024-03-24 14:15:04 +08:00
kidv
29e03b88c7 Fix: Handle empty server response in API call 2024-03-24 04:07:25 +08:00
fred-bf
ebbd870150 Merge pull request #4353 from H0llyW00dzZ/cherry-pick-webdav
[Cherry Pick] Fix Webdav Syncing Issues
2024-03-21 18:56:26 +08:00
H0llyW00dzZ
c0c54e5709 Fix Webdav Syncing Issues
- [+] feat(route.ts): add endpoint validation and improve error handling
- [+] refactor(route.ts): use targetPath for request validation and error messages
- [+] fix(route.ts): correct targetUrl formation
2024-03-20 01:40:41 +07:00
l.tingting
ed8099bf1e Update README.md 2024-03-02 15:26:19 +08:00
l.tingting
524c9beee4 support \(...\) and \[...\] style math formula 2024-03-02 11:08:34 +08:00
H0llyW00dzZ
bf1b5c3951 Update Docker Ignore
- [+] chore(dockerignore): update .dockerignore file with more comprehensive ignore rules
2024-02-21 08:46:21 +07:00
Dup4
86f42d56f2 fix: webdav check httpcode list
Signed-off-by: Dup4 <lyuzhi.pan@gmail.com>
2024-01-18 09:11:13 +08:00
dependabot[bot]
f05bf0a6f6 chore(deps-dev): bump eslint-plugin-prettier from 4.2.1 to 5.1.3
Bumps [eslint-plugin-prettier](https://github.com/prettier/eslint-plugin-prettier) from 4.2.1 to 5.1.3.
- [Release notes](https://github.com/prettier/eslint-plugin-prettier/releases)
- [Changelog](https://github.com/prettier/eslint-plugin-prettier/blob/master/CHANGELOG.md)
- [Commits](https://github.com/prettier/eslint-plugin-prettier/compare/v4.2.1...v5.1.3)

---
updated-dependencies:
- dependency-name: eslint-plugin-prettier
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-15 10:20:50 +00:00
Eric Huang
943a2707d2 fix(chat-item): selected chat-item showing border in other pages 2023-12-15 09:37:37 +08:00
reece00
1442337e3c The language filtering option of the mask is stored 2023-12-12 02:22:22 +08:00
H0llyW00dzZ
8dc8682078 Fix Api Common [Server Side]
- [+] fix(common.ts): improve handling of OpenAI-Organization header
 - Check if serverConfig.openaiOrgId is defined and not an empty string
 - Log the value of openaiOrganizationHeader if present, otherwise log that the header is not present
 - Conditionally delete the OpenAI-Organization header from the response if [Org ID] is undefined or empty (not setup in ENV)
2023-12-04 13:33:23 +07:00
H0llyW00dzZ
36e9c6ac4d Refactor Api Common [Server Side] [Console Log]
- [+] refactor(common.ts): remove unnecessary console.log for [Org ID] in requestOpenai function
- [+] refactor(common.ts): conditionally delete OpenAI-Organization header from response if [Org ID] is not set up in ENV
2023-12-01 19:49:12 +07:00
frankylli
10ea9bf1e3 fix: MessageSelectorWarning 2023-11-29 16:25:15 +08:00
H0llyW00dzZ
fe0f078353 Feat ChatGPT LLM Api [Console Log] [Text Moderation] [Azure]
[+] fix(openai.ts): fix parsing error in ChatGPTApi's message handler
[+] feat(openai.ts): add logging for flagged categories in text moderation
2023-11-19 19:49:52 +07:00
SurKaa
39f3afd52c Update .env.template 更正单词 2023-11-16 09:22:56 +08:00
H0llyW00dzZ
544bab0fe2 Refactor Summarize Logic
[+] chore(chat.ts): remove unnecessary comment and refactor variable name
[+] feat(chat.ts): add stream: false to config object
2023-11-09 20:56:45 +07:00
Yidadaa
cdf0311d27 feat: add claude and bard 2023-11-07 23:22:11 +08:00
Yidadaa
5610f423d0 feat: add multi-model support 2023-10-30 02:07:11 +08:00
34 changed files with 1231 additions and 210 deletions

View File

@@ -1,8 +1,97 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Node.js dependencies
/node_modules
/jspm_packages
# TypeScript v1 declaration files
typings
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.test
# local env files
.env*.local
# docker-compose env files
.env
# Next.js build output
.next
out
# Nuxt.js build output
.nuxt
dist
# Gatsby files
.cache/
# Vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# Temporary folders
tmp
temp
# IDE and editor directories
.idea
.vscode
*.swp
*.swo
*~
# OS generated files
.DS_Store
Thumbs.db
# secret key
*.key
*.key.pub
*.key.pub

View File

@@ -2,7 +2,7 @@
# Your openai api key. (required)
OPENAI_API_KEY=sk-xxxx
# Access passsword, separated by comma. (optional)
# Access password, separated by comma. (optional)
CODE=your-password
# You can start service behind a proxy

View File

@@ -216,7 +216,7 @@ If you do not want users to use GPT-4, set this value to 1.
> Default: Empty
If you do want users to query balance, set this value to 1, or you should set it to 0.
If you do want users to query balance, set this value to 1.
### `DISABLE_FAST_LINK` (optional)

View File

@@ -0,0 +1,189 @@
import { getServerSideConfig } from "@/app/config/server";
import {
ANTHROPIC_BASE_URL,
Anthropic,
ApiPath,
DEFAULT_MODELS,
ModelProvider,
} from "@/app/constant";
import { prettyObject } from "@/app/utils/format";
import { NextRequest, NextResponse } from "next/server";
import { auth } from "../../auth";
import { collectModelTable } from "@/app/utils/model";
const ALLOWD_PATH = new Set([Anthropic.ChatPath, Anthropic.ChatPath1]);
async function handle(
req: NextRequest,
{ params }: { params: { path: string[] } },
) {
console.log("[Anthropic Route] params ", params);
if (req.method === "OPTIONS") {
return NextResponse.json({ body: "OK" }, { status: 200 });
}
const subpath = params.path.join("/");
if (!ALLOWD_PATH.has(subpath)) {
console.log("[Anthropic Route] forbidden path ", subpath);
return NextResponse.json(
{
error: true,
msg: "you are not allowed to request " + subpath,
},
{
status: 403,
},
);
}
const authResult = auth(req, ModelProvider.Claude);
if (authResult.error) {
return NextResponse.json(authResult, {
status: 401,
});
}
try {
const response = await request(req);
return response;
} catch (e) {
console.error("[Anthropic] ", e);
return NextResponse.json(prettyObject(e));
}
}
export const GET = handle;
export const POST = handle;
export const runtime = "edge";
export const preferredRegion = [
"arn1",
"bom1",
"cdg1",
"cle1",
"cpt1",
"dub1",
"fra1",
"gru1",
"hnd1",
"iad1",
"icn1",
"kix1",
"lhr1",
"pdx1",
"sfo1",
"sin1",
"syd1",
];
const serverConfig = getServerSideConfig();
async function request(req: NextRequest) {
const controller = new AbortController();
let authHeaderName = "x-api-key";
let authValue =
req.headers.get(authHeaderName) ||
req.headers.get("Authorization")?.replaceAll("Bearer ", "").trim() ||
serverConfig.anthropicApiKey ||
"";
let path = `${req.nextUrl.pathname}`.replaceAll(ApiPath.Anthropic, "");
let baseUrl =
serverConfig.anthropicUrl || serverConfig.baseUrl || ANTHROPIC_BASE_URL;
if (!baseUrl.startsWith("http")) {
baseUrl = `https://${baseUrl}`;
}
if (baseUrl.endsWith("/")) {
baseUrl = baseUrl.slice(0, -1);
}
console.log("[Proxy] ", path);
console.log("[Base Url]", baseUrl);
const timeoutId = setTimeout(
() => {
controller.abort();
},
10 * 60 * 1000,
);
const fetchUrl = `${baseUrl}${path}`;
const fetchOptions: RequestInit = {
headers: {
"Content-Type": "application/json",
"Cache-Control": "no-store",
[authHeaderName]: authValue,
"anthropic-version":
req.headers.get("anthropic-version") ||
serverConfig.anthropicApiVersion ||
Anthropic.Vision,
},
method: req.method,
body: req.body,
redirect: "manual",
// @ts-ignore
duplex: "half",
signal: controller.signal,
};
// #1815 try to refuse some request to some models
if (serverConfig.customModels && req.body) {
try {
const modelTable = collectModelTable(
DEFAULT_MODELS,
serverConfig.customModels,
);
const clonedBody = await req.text();
fetchOptions.body = clonedBody;
const jsonBody = JSON.parse(clonedBody) as { model?: string };
// not undefined and is false
if (modelTable[jsonBody?.model ?? ""].available === false) {
return NextResponse.json(
{
error: true,
message: `you are not allowed to use ${jsonBody?.model} model`,
},
{
status: 403,
},
);
}
} catch (e) {
console.error(`[Anthropic] filter`, e);
}
}
console.log("[Anthropic request]", fetchOptions.headers, req.method);
try {
const res = await fetch(fetchUrl, fetchOptions);
console.log(
"[Anthropic response]",
res.status,
" ",
res.headers,
res.url,
);
// to prevent browser prompt for credentials
const newHeaders = new Headers(res.headers);
newHeaders.delete("www-authenticate");
// to disable nginx buffering
newHeaders.set("X-Accel-Buffering", "no");
return new Response(res.body, {
status: res.status,
statusText: res.statusText,
headers: newHeaders,
});
} finally {
clearTimeout(timeoutId);
}
}

View File

@@ -57,12 +57,31 @@ export function auth(req: NextRequest, modelProvider: ModelProvider) {
if (!apiKey) {
const serverConfig = getServerSideConfig();
const systemApiKey =
modelProvider === ModelProvider.GeminiPro
? serverConfig.googleApiKey
: serverConfig.isAzure
? serverConfig.azureApiKey
: serverConfig.apiKey;
// const systemApiKey =
// modelProvider === ModelProvider.GeminiPro
// ? serverConfig.googleApiKey
// : serverConfig.isAzure
// ? serverConfig.azureApiKey
// : serverConfig.apiKey;
let systemApiKey: string | undefined;
switch (modelProvider) {
case ModelProvider.GeminiPro:
systemApiKey = serverConfig.googleApiKey;
break;
case ModelProvider.Claude:
systemApiKey = serverConfig.anthropicApiKey;
break;
case ModelProvider.GPT:
default:
if (serverConfig.isAzure) {
systemApiKey = serverConfig.azureApiKey;
} else {
systemApiKey = serverConfig.apiKey;
}
}
if (systemApiKey) {
console.log("[Auth] use system api key");
req.headers.set("Authorization", `Bearer ${systemApiKey}`);

View File

@@ -43,10 +43,6 @@ export async function requestOpenai(req: NextRequest) {
console.log("[Proxy] ", path);
console.log("[Base Url]", baseUrl);
// this fix [Org ID] undefined in server side if not using custom point
if (serverConfig.openaiOrgId !== undefined) {
console.log("[Org ID]", serverConfig.openaiOrgId);
}
const timeoutId = setTimeout(
() => {
@@ -116,18 +112,37 @@ export async function requestOpenai(req: NextRequest) {
try {
const res = await fetch(fetchUrl, fetchOptions);
// Extract the OpenAI-Organization header from the response
const openaiOrganizationHeader = res.headers.get("OpenAI-Organization");
// Check if serverConfig.openaiOrgId is defined and not an empty string
if (serverConfig.openaiOrgId && serverConfig.openaiOrgId.trim() !== "") {
// If openaiOrganizationHeader is present, log it; otherwise, log that the header is not present
console.log("[Org ID]", openaiOrganizationHeader);
} else {
console.log("[Org ID] is not set up.");
}
// to prevent browser prompt for credentials
const newHeaders = new Headers(res.headers);
newHeaders.delete("www-authenticate");
// to disable nginx buffering
newHeaders.set("X-Accel-Buffering", "no");
// Conditionally delete the OpenAI-Organization header from the response if [Org ID] is undefined or empty (not setup in ENV)
// Also, this is to prevent the header from being sent to the client
if (!serverConfig.openaiOrgId || serverConfig.openaiOrgId.trim() === "") {
newHeaders.delete("OpenAI-Organization");
}
// The latest version of the OpenAI API forced the content-encoding to be "br" in json response
// So if the streaming is disabled, we need to remove the content-encoding header
// Because Vercel uses gzip to compress the response, if we don't remove the content-encoding header
// The browser will try to decode the response with brotli and fail
newHeaders.delete("content-encoding");
return new Response(res.body, {
status: res.status,
statusText: res.statusText,

View File

@@ -12,17 +12,28 @@ async function handle(
const requestUrl = new URL(req.url);
let endpoint = requestUrl.searchParams.get("endpoint");
if (!endpoint?.endsWith("/")) {
endpoint += "/";
// Validate the endpoint to prevent potential SSRF attacks
if (!endpoint || !endpoint.startsWith("/")) {
return NextResponse.json(
{
error: true,
msg: "Invalid endpoint",
},
{
status: 400,
},
);
}
const endpointPath = params.path.join("/");
const targetPath = `${endpoint}/${endpointPath}`;
// only allow MKCOL, GET, PUT
if (req.method !== "MKCOL" && req.method !== "GET" && req.method !== "PUT") {
return NextResponse.json(
{
error: true,
msg: "you are not allowed to request " + params.path.join("/"),
msg: "you are not allowed to request " + targetPath,
},
{
status: 403,
@@ -32,13 +43,13 @@ async function handle(
// for MKCOL request, only allow request ${folder}
if (
req.method == "MKCOL" &&
!new URL(endpointPath).pathname.endsWith(folder)
req.method === "MKCOL" &&
!targetPath.endsWith(folder)
) {
return NextResponse.json(
{
error: true,
msg: "you are not allowed to request " + params.path.join("/"),
msg: "you are not allowed to request " + targetPath,
},
{
status: 403,
@@ -48,13 +59,13 @@ async function handle(
// for GET request, only allow request ending with fileName
if (
req.method == "GET" &&
!new URL(endpointPath).pathname.endsWith(fileName)
req.method === "GET" &&
!targetPath.endsWith(fileName)
) {
return NextResponse.json(
{
error: true,
msg: "you are not allowed to request " + params.path.join("/"),
msg: "you are not allowed to request " + targetPath,
},
{
status: 403,
@@ -64,13 +75,13 @@ async function handle(
// for PUT request, only allow request ending with fileName
if (
req.method == "PUT" &&
!new URL(endpointPath).pathname.endsWith(fileName)
req.method === "PUT" &&
!targetPath.endsWith(fileName)
) {
return NextResponse.json(
{
error: true,
msg: "you are not allowed to request " + params.path.join("/"),
msg: "you are not allowed to request " + targetPath,
},
{
status: 403,
@@ -78,7 +89,7 @@ async function handle(
);
}
const targetUrl = `${endpoint + endpointPath}`;
const targetUrl = `${endpoint}/${endpointPath}`;
const method = req.method;
const shouldNotHaveBody = ["get", "head"].includes(
@@ -90,6 +101,7 @@ async function handle(
authorization: req.headers.get("authorization") ?? "",
},
body: shouldNotHaveBody ? null : req.body,
redirect: 'manual',
method,
// @ts-ignore
duplex: "half",

View File

@@ -8,6 +8,7 @@ import {
import { ChatMessage, ModelType, useAccessStore, useChatStore } from "../store";
import { ChatGPTApi } from "./platforms/openai";
import { GeminiProApi } from "./platforms/google";
import { ClaudeApi } from "./platforms/anthropic";
export const ROLES = ["system", "user", "assistant"] as const;
export type MessageRole = (typeof ROLES)[number];
@@ -94,11 +95,16 @@ export class ClientApi {
public llm: LLMApi;
constructor(provider: ModelProvider = ModelProvider.GPT) {
if (provider === ModelProvider.GeminiPro) {
this.llm = new GeminiProApi();
return;
switch (provider) {
case ModelProvider.GeminiPro:
this.llm = new GeminiProApi();
break;
case ModelProvider.Claude:
this.llm = new ClaudeApi();
break;
default:
this.llm = new ChatGPTApi();
}
this.llm = new ChatGPTApi();
}
config() {}

View File

@@ -0,0 +1,404 @@
import { ACCESS_CODE_PREFIX, Anthropic, ApiPath } from "@/app/constant";
import { ChatOptions, LLMApi, MultimodalContent } from "../api";
import { useAccessStore, useAppConfig, useChatStore } from "@/app/store";
import { getClientConfig } from "@/app/config/client";
import { DEFAULT_API_HOST } from "@/app/constant";
import { RequestMessage } from "@/app/typing";
import {
EventStreamContentType,
fetchEventSource,
} from "@fortaine/fetch-event-source";
import Locale from "../../locales";
import { prettyObject } from "@/app/utils/format";
import { getMessageTextContent, isVisionModel } from "@/app/utils";
export type MultiBlockContent = {
type: "image" | "text";
source?: {
type: string;
media_type: string;
data: string;
};
text?: string;
};
export type AnthropicMessage = {
role: (typeof ClaudeMapper)[keyof typeof ClaudeMapper];
content: string | MultiBlockContent[];
};
export interface AnthropicChatRequest {
model: string; // The model that will complete your prompt.
messages: AnthropicMessage[]; // The prompt that you want Claude to complete.
max_tokens: number; // The maximum number of tokens to generate before stopping.
stop_sequences?: string[]; // Sequences that will cause the model to stop generating completion text.
temperature?: number; // Amount of randomness injected into the response.
top_p?: number; // Use nucleus sampling.
top_k?: number; // Only sample from the top K options for each subsequent token.
metadata?: object; // An object describing metadata about the request.
stream?: boolean; // Whether to incrementally stream the response using server-sent events.
}
export interface ChatRequest {
model: string; // The model that will complete your prompt.
prompt: string; // The prompt that you want Claude to complete.
max_tokens_to_sample: number; // The maximum number of tokens to generate before stopping.
stop_sequences?: string[]; // Sequences that will cause the model to stop generating completion text.
temperature?: number; // Amount of randomness injected into the response.
top_p?: number; // Use nucleus sampling.
top_k?: number; // Only sample from the top K options for each subsequent token.
metadata?: object; // An object describing metadata about the request.
stream?: boolean; // Whether to incrementally stream the response using server-sent events.
}
export interface ChatResponse {
completion: string;
stop_reason: "stop_sequence" | "max_tokens";
model: string;
}
export type ChatStreamResponse = ChatResponse & {
stop?: string;
log_id: string;
};
const ClaudeMapper = {
assistant: "assistant",
user: "user",
system: "user",
} as const;
const keys = ["claude-2, claude-instant-1"];
export class ClaudeApi implements LLMApi {
extractMessage(res: any) {
console.log("[Response] claude response: ", res);
return res?.content?.[0]?.text;
}
async chat(options: ChatOptions): Promise<void> {
const visionModel = isVisionModel(options.config.model);
const accessStore = useAccessStore.getState();
const shouldStream = !!options.config.stream;
const modelConfig = {
...useAppConfig.getState().modelConfig,
...useChatStore.getState().currentSession().mask.modelConfig,
...{
model: options.config.model,
},
};
const messages = [...options.messages];
const keys = ["system", "user"];
// roles must alternate between "user" and "assistant" in claude, so add a fake assistant message between two user messages
for (let i = 0; i < messages.length - 1; i++) {
const message = messages[i];
const nextMessage = messages[i + 1];
if (keys.includes(message.role) && keys.includes(nextMessage.role)) {
messages[i] = [
message,
{
role: "assistant",
content: ";",
},
] as any;
}
}
const prompt = messages
.flat()
.filter((v) => {
if (!v.content) return false;
if (typeof v.content === "string" && !v.content.trim()) return false;
return true;
})
.map((v) => {
const { role, content } = v;
const insideRole = ClaudeMapper[role] ?? "user";
if (!visionModel || typeof content === "string") {
return {
role: insideRole,
content: getMessageTextContent(v),
};
}
return {
role: insideRole,
content: content
.filter((v) => v.image_url || v.text)
.map(({ type, text, image_url }) => {
if (type === "text") {
return {
type,
text: text!,
};
}
const { url = "" } = image_url || {};
const colonIndex = url.indexOf(":");
const semicolonIndex = url.indexOf(";");
const comma = url.indexOf(",");
const mimeType = url.slice(colonIndex + 1, semicolonIndex);
const encodeType = url.slice(semicolonIndex + 1, comma);
const data = url.slice(comma + 1);
return {
type: "image" as const,
source: {
type: encodeType,
media_type: mimeType,
data,
},
};
}),
};
});
const requestBody: AnthropicChatRequest = {
messages: prompt,
stream: shouldStream,
model: modelConfig.model,
max_tokens: modelConfig.max_tokens,
temperature: modelConfig.temperature,
top_p: modelConfig.top_p,
// top_k: modelConfig.top_k,
top_k: 5,
};
const path = this.path(Anthropic.ChatPath);
const controller = new AbortController();
options.onController?.(controller);
const payload = {
method: "POST",
body: JSON.stringify(requestBody),
signal: controller.signal,
headers: {
"Content-Type": "application/json",
Accept: "application/json",
"x-api-key": accessStore.anthropicApiKey,
"anthropic-version": accessStore.anthropicApiVersion,
Authorization: getAuthKey(accessStore.anthropicApiKey),
},
};
if (shouldStream) {
try {
const context = {
text: "",
finished: false,
};
const finish = () => {
if (!context.finished) {
options.onFinish(context.text);
context.finished = true;
}
};
controller.signal.onabort = finish;
fetchEventSource(path, {
...payload,
async onopen(res) {
const contentType = res.headers.get("content-type");
console.log("response content type: ", contentType);
if (contentType?.startsWith("text/plain")) {
context.text = await res.clone().text();
return finish();
}
if (
!res.ok ||
!res.headers
.get("content-type")
?.startsWith(EventStreamContentType) ||
res.status !== 200
) {
const responseTexts = [context.text];
let extraInfo = await res.clone().text();
try {
const resJson = await res.clone().json();
extraInfo = prettyObject(resJson);
} catch {}
if (res.status === 401) {
responseTexts.push(Locale.Error.Unauthorized);
}
if (extraInfo) {
responseTexts.push(extraInfo);
}
context.text = responseTexts.join("\n\n");
return finish();
}
},
onmessage(msg) {
let chunkJson:
| undefined
| {
type: "content_block_delta" | "content_block_stop";
delta?: {
type: "text_delta";
text: string;
};
index: number;
};
try {
chunkJson = JSON.parse(msg.data);
} catch (e) {
console.error("[Response] parse error", msg.data);
}
if (!chunkJson || chunkJson.type === "content_block_stop") {
return finish();
}
const { delta } = chunkJson;
if (delta?.text) {
context.text += delta.text;
options.onUpdate?.(context.text, delta.text);
}
},
onclose() {
finish();
},
onerror(e) {
options.onError?.(e);
throw e;
},
openWhenHidden: true,
});
} catch (e) {
console.error("failed to chat", e);
options.onError?.(e as Error);
}
} else {
try {
controller.signal.onabort = () => options.onFinish("");
const res = await fetch(path, payload);
const resJson = await res.json();
const message = this.extractMessage(resJson);
options.onFinish(message);
} catch (e) {
console.error("failed to chat", e);
options.onError?.(e as Error);
}
}
}
async usage() {
return {
used: 0,
total: 0,
};
}
async models() {
// const provider = {
// id: "anthropic",
// providerName: "Anthropic",
// providerType: "anthropic",
// };
return [
// {
// name: "claude-instant-1.2",
// available: true,
// provider,
// },
// {
// name: "claude-2.0",
// available: true,
// provider,
// },
// {
// name: "claude-2.1",
// available: true,
// provider,
// },
// {
// name: "claude-3-opus-20240229",
// available: true,
// provider,
// },
// {
// name: "claude-3-sonnet-20240229",
// available: true,
// provider,
// },
// {
// name: "claude-3-haiku-20240307",
// available: true,
// provider,
// },
];
}
path(path: string): string {
const accessStore = useAccessStore.getState();
let baseUrl: string = accessStore.anthropicUrl;
// if endpoint is empty, use default endpoint
if (baseUrl.trim().length === 0) {
const isApp = !!getClientConfig()?.isApp;
baseUrl = isApp
? DEFAULT_API_HOST + "/api/proxy/anthropic"
: ApiPath.Anthropic;
}
if (!baseUrl.startsWith("http") && !baseUrl.startsWith("/api")) {
baseUrl = "https://" + baseUrl;
}
baseUrl = trimEnd(baseUrl, "/");
return `${baseUrl}/${path}`;
}
}
function trimEnd(s: string, end = " ") {
if (end.length === 0) return s;
while (s.endsWith(end)) {
s = s.slice(0, -end.length);
}
return s;
}
function bearer(value: string) {
return `Bearer ${value.trim()}`;
}
function getAuthKey(apiKey = "") {
const accessStore = useAccessStore.getState();
const isApp = !!getClientConfig()?.isApp;
let authKey = "";
if (apiKey) {
// use user's api key first
authKey = bearer(apiKey);
} else if (
accessStore.enabledAccessControl() &&
!isApp &&
!!accessStore.accessCode
) {
// or use access code
authKey = bearer(ACCESS_CODE_PREFIX + accessStore.accessCode);
}
return authKey;
}

View File

@@ -151,6 +151,9 @@ export class ChatGPTApi implements LLMApi {
if (finished || controller.signal.aborted) {
responseText += remainText;
console.log("[Response Animation] finished");
if (responseText?.length === 0) {
options.onError?.(new Error("empty response from server"));
}
return;
}
@@ -225,19 +228,21 @@ export class ChatGPTApi implements LLMApi {
}
const text = msg.data;
try {
const json = JSON.parse(text) as {
choices: Array<{
delta: {
content: string;
};
}>;
};
const delta = json.choices[0]?.delta?.content;
const json = JSON.parse(text);
const choices = json.choices as Array<{ delta: { content: string } }>;
const delta = choices[0]?.delta?.content;
const textmoderation = json?.prompt_filter_results;
if (delta) {
remainText += delta;
}
if (textmoderation && textmoderation.length > 0 && ServiceProvider.Azure) {
const contentFilterResults = textmoderation[0]?.content_filter_results;
console.log(`[${ServiceProvider.Azure}] [Text Moderation] flagged categories result:`, contentFilterResults);
}
} catch (e) {
console.error("[Request] parse error", text);
console.error("[Request] parse error", text, msg);
}
},
onclose() {

View File

@@ -20,6 +20,8 @@ export function AuthPage() {
accessStore.update((access) => {
access.openaiApiKey = "";
access.accessCode = "";
access.googleApiKey = "";
access.anthropicApiKey = "";
});
}; // Reset access code to empty string
@@ -75,6 +77,17 @@ export function AuthPage() {
);
}}
/>
<input
className={styles["auth-input"]}
type="password"
placeholder={Locale.Settings.Access.Anthropic.ApiKey.Placeholder}
value={accessStore.anthropicApiKey}
onChange={(e) => {
accessStore.update(
(access) => (access.anthropicApiKey = e.currentTarget.value),
);
}}
/>
</>
) : null}

View File

@@ -12,7 +12,7 @@ import {
import { useChatStore } from "../store";
import Locale from "../locales";
import { Link, useNavigate } from "react-router-dom";
import { Link, useLocation, useNavigate } from "react-router-dom";
import { Path } from "../constant";
import { MaskAvatar } from "./mask";
import { Mask } from "../store/mask";
@@ -40,12 +40,16 @@ export function ChatItem(props: {
});
}
}, [props.selected]);
const { pathname: currentPath } = useLocation();
return (
<Draggable draggableId={`${props.id}`} index={props.index}>
{(provided) => (
<div
className={`${styles["chat-item"]} ${
props.selected && styles["chat-item-selected"]
props.selected &&
(currentPath === Path.Chat || currentPath === Path.Home) &&
styles["chat-item-selected"]
}`}
onClick={props.onClick}
ref={(ele) => {

View File

@@ -315,6 +315,8 @@ export function PreviewActions(props: {
var api: ClientApi;
if (config.modelConfig.model.startsWith("gemini")) {
api = new ClientApi(ModelProvider.GeminiPro);
} else if (config.modelConfig.model.startsWith("claude")) {
api = new ClientApi(ModelProvider.Claude);
} else {
api = new ClientApi(ModelProvider.GPT);
}

View File

@@ -173,6 +173,8 @@ export function useLoadData() {
var api: ClientApi;
if (config.modelConfig.model.startsWith("gemini")) {
api = new ClientApi(ModelProvider.GeminiPro);
} else if (config.modelConfig.model.startsWith("claude")) {
api = new ClientApi(ModelProvider.Claude);
} else {
api = new ClientApi(ModelProvider.GPT);
}

View File

@@ -116,9 +116,27 @@ function escapeDollarNumber(text: string) {
return escapedText;
}
function escapeBrackets(text: string) {
const pattern =
/(```[\s\S]*?```|`.*?`)|\\\[([\s\S]*?[^\\])\\\]|\\\((.*?)\\\)/g;
return text.replace(
pattern,
(match, codeBlock, squareBracket, roundBracket) => {
if (codeBlock) {
return codeBlock;
} else if (squareBracket) {
return `$$${squareBracket}$$`;
} else if (roundBracket) {
return `$${roundBracket}$`;
}
return match;
},
);
}
function _MarkDownContent(props: { content: string }) {
const escapedContent = useMemo(
() => escapeDollarNumber(props.content),
() => escapeBrackets(escapeDollarNumber(props.content)),
[props.content],
);

View File

@@ -404,7 +404,16 @@ export function MaskPage() {
const maskStore = useMaskStore();
const chatStore = useChatStore();
const [filterLang, setFilterLang] = useState<Lang>();
const [filterLang, setFilterLang] = useState<Lang | undefined>(
localStorage.getItem("Mask-language") as Lang | undefined,
);
useEffect(() => {
if (filterLang) {
localStorage.setItem("Mask-language", filterLang);
} else {
localStorage.removeItem("Mask-language");
}
}, [filterLang]);
const allMasks = maskStore
.getAll()

View File

@@ -227,7 +227,7 @@ export function MessageSelector(props: {
</div>
<div className={styles["checkbox"]}>
<input type="checkbox" checked={isSelected}></input>
<input type="checkbox" checked={isSelected} readOnly></input>
</div>
</div>
);

View File

@@ -51,6 +51,7 @@ import Locale, {
import { copyToClipboard } from "../utils";
import Link from "next/link";
import {
Anthropic,
Azure,
Google,
OPENAI_BASE_URL,
@@ -963,7 +964,7 @@ export function Settings() {
</Select>
</ListItem>
{accessStore.provider === "OpenAI" ? (
{accessStore.provider === ServiceProvider.OpenAI && (
<>
<ListItem
title={Locale.Settings.Access.OpenAI.Endpoint.Title}
@@ -1002,7 +1003,8 @@ export function Settings() {
/>
</ListItem>
</>
) : accessStore.provider === "Azure" ? (
)}
{accessStore.provider === ServiceProvider.Azure && (
<>
<ListItem
title={Locale.Settings.Access.Azure.Endpoint.Title}
@@ -1061,7 +1063,8 @@ export function Settings() {
></input>
</ListItem>
</>
) : accessStore.provider === "Google" ? (
)}
{accessStore.provider === ServiceProvider.Google && (
<>
<ListItem
title={Locale.Settings.Access.Google.Endpoint.Title}
@@ -1120,7 +1123,70 @@ export function Settings() {
></input>
</ListItem>
</>
) : null}
)}
{accessStore.provider === ServiceProvider.Anthropic && (
<>
<ListItem
title={Locale.Settings.Access.Anthropic.Endpoint.Title}
subTitle={
Locale.Settings.Access.Anthropic.Endpoint.SubTitle +
Anthropic.ExampleEndpoint
}
>
<input
type="text"
value={accessStore.anthropicUrl}
placeholder={Anthropic.ExampleEndpoint}
onChange={(e) =>
accessStore.update(
(access) =>
(access.anthropicUrl = e.currentTarget.value),
)
}
></input>
</ListItem>
<ListItem
title={Locale.Settings.Access.Anthropic.ApiKey.Title}
subTitle={
Locale.Settings.Access.Anthropic.ApiKey.SubTitle
}
>
<PasswordInput
value={accessStore.anthropicApiKey}
type="text"
placeholder={
Locale.Settings.Access.Anthropic.ApiKey.Placeholder
}
onChange={(e) => {
accessStore.update(
(access) =>
(access.anthropicApiKey =
e.currentTarget.value),
);
}}
/>
</ListItem>
<ListItem
title={Locale.Settings.Access.Anthropic.ApiVerion.Title}
subTitle={
Locale.Settings.Access.Anthropic.ApiVerion.SubTitle
}
>
<input
type="text"
value={accessStore.anthropicApiVersion}
placeholder={Anthropic.Vision}
onChange={(e) =>
accessStore.update(
(access) =>
(access.anthropicApiVersion =
e.currentTarget.value),
)
}
></input>
</ListItem>
</>
)}
</>
)}
</>

View File

@@ -69,6 +69,7 @@ export const getServerSideConfig = () => {
const isAzure = !!process.env.AZURE_URL;
const isGoogle = !!process.env.GOOGLE_API_KEY;
const isAnthropic = !!process.env.ANTHROPIC_API_KEY;
const apiKeyEnvVar = process.env.OPENAI_API_KEY ?? "";
const apiKeys = apiKeyEnvVar.split(",").map((v) => v.trim());
@@ -92,6 +93,11 @@ export const getServerSideConfig = () => {
googleApiKey: process.env.GOOGLE_API_KEY,
googleUrl: process.env.GOOGLE_URL,
isAnthropic,
anthropicApiKey: process.env.ANTHROPIC_API_KEY,
anthropicApiVersion: process.env.ANTHROPIC_API_VERSION,
anthropicUrl: process.env.ANTHROPIC_URL,
gtmId: process.env.GTM_ID,
needCode: ACCESS_CODES.size > 0,

View File

@@ -10,6 +10,7 @@ export const RUNTIME_CONFIG_DOM = "danger-runtime-config";
export const DEFAULT_API_HOST = "https://api.nextchat.dev";
export const OPENAI_BASE_URL = "https://api.openai.com";
export const ANTHROPIC_BASE_URL = "https://api.anthropic.com";
export const GEMINI_BASE_URL = "https://generativelanguage.googleapis.com/";
@@ -25,6 +26,7 @@ export enum Path {
export enum ApiPath {
Cors = "",
OpenAI = "/api/openai",
Anthropic = "/api/anthropic",
}
export enum SlotID {
@@ -67,13 +69,22 @@ export enum ServiceProvider {
OpenAI = "OpenAI",
Azure = "Azure",
Google = "Google",
Anthropic = "Anthropic",
}
export enum ModelProvider {
GPT = "GPT",
GeminiPro = "GeminiPro",
Claude = "Claude",
}
export const Anthropic = {
ChatPath: "v1/messages",
ChatPath1: "v1/complete",
ExampleEndpoint: "https://api.anthropic.com",
Vision: "2023-06-01",
};
export const OpenaiPath = {
ChatPath: "v1/chat/completions",
UsagePath: "dashboard/billing/usage",
@@ -289,6 +300,60 @@ export const DEFAULT_MODELS = [
providerType: "google",
},
},
{
name: "claude-instant-1.2",
available: true,
provider: {
id: "anthropic",
providerName: "Anthropic",
providerType: "anthropic",
},
},
{
name: "claude-2.0",
available: true,
provider: {
id: "anthropic",
providerName: "Anthropic",
providerType: "anthropic",
},
},
{
name: "claude-2.1",
available: true,
provider: {
id: "anthropic",
providerName: "Anthropic",
providerType: "anthropic",
},
},
{
name: "claude-3-opus-20240229",
available: true,
provider: {
id: "anthropic",
providerName: "Anthropic",
providerType: "anthropic",
},
},
{
name: "claude-3-sonnet-20240229",
available: true,
provider: {
id: "anthropic",
providerName: "Anthropic",
providerType: "anthropic",
},
},
{
name: "claude-3-haiku-20240307",
available: true,
provider: {
id: "anthropic",
providerName: "Anthropic",
providerType: "anthropic",
},
},
] as const;
export const CHAT_PAGE_SIZE = 15;

View File

@@ -13,7 +13,7 @@ const cn = {
Auth: {
Title: "需要密码",
Tips: "管理员开启了密码验证,请在下方填入访问码",
SubTips: "或者输入你的 OpenAI Google API 密钥",
SubTips: "或者输入你的 OpenAI, Google API 或 Anthropic API 密钥",
Input: "在此处填写访问码",
Confirm: "确认",
Later: "稍后再说",
@@ -313,6 +313,23 @@ const cn = {
SubTitle: "选择指定的部分版本",
},
},
Anthropic: {
ApiKey: {
Title: "接口密钥",
SubTitle: "使用自定义 Anthropic Key 绕过密码访问限制",
Placeholder: "输入您的 Anthropic API 密钥",
},
Endpoint: {
Title: "接口地址",
SubTitle: "样例:",
},
ApiVerion: {
Title: "接口版本 (claude api version)",
SubTitle: "选择一个特定的 API 版本输入",
},
},
Google: {
ApiKey: {
Title: "API 密钥",

View File

@@ -15,7 +15,7 @@ const en: LocaleType = {
Auth: {
Title: "Need Access Code",
Tips: "Please enter access code below",
SubTips: "Or enter your OpenAI or Google API Key",
SubTips: "Or enter your OpenAI, Google API Key or Anthropic API Key",
Input: "access code",
Confirm: "Confirm",
Later: "Later",
@@ -316,6 +316,24 @@ const en: LocaleType = {
SubTitle: "Check your api version from azure console",
},
},
Anthropic: {
ApiKey: {
Title: "Anthropic API Key",
SubTitle:
"Use a custom Anthropic Key to bypass password access restrictions",
Placeholder: "Enter your Anthropic API Key",
},
Endpoint: {
Title: "Endpoint Address",
SubTitle: "Example:",
},
ApiVerion: {
Title: "API Version (claude api version)",
SubTitle: "Select and input a specific API version",
},
},
CustomModel: {
Title: "Custom Models",
SubTitle: "Custom model options, seperated by comma",

View File

@@ -316,6 +316,23 @@ const pt: PartialLocaleType = {
SubTitle: "Verifique sua versão API do console Azure",
},
},
Anthropic: {
ApiKey: {
Title: "Chave API Anthropic",
SubTitle: "Verifique sua chave API do console Anthropic",
Placeholder: "Chave API Anthropic",
},
Endpoint: {
Title: "Endpoint Address",
SubTitle: "Exemplo: ",
},
ApiVerion: {
Title: "Versão API (Versão api claude)",
SubTitle: "Verifique sua versão API do console Anthropic",
},
},
CustomModel: {
Title: "Modelos Personalizados",
SubTitle: "Opções de modelo personalizado, separados por vírgula",

View File

@@ -317,6 +317,23 @@ const sk: PartialLocaleType = {
SubTitle: "Skontrolujte svoju verziu API v Azure konzole",
},
},
Anthropic: {
ApiKey: {
Title: "API kľúč Anthropic",
SubTitle: "Skontrolujte svoj API kľúč v Anthropic konzole",
Placeholder: "API kľúč Anthropic",
},
Endpoint: {
Title: "Adresa koncového bodu",
SubTitle: "Príklad:",
},
ApiVerion: {
Title: "Verzia API (claude verzia API)",
SubTitle: "Vyberte špecifickú verziu časti",
},
},
CustomModel: {
Title: "Vlastné modely",
SubTitle: "Možnosti vlastného modelu, oddelené čiarkou",

View File

@@ -314,6 +314,23 @@ const tw = {
SubTitle: "選擇指定的部分版本",
},
},
Anthropic: {
ApiKey: {
Title: "API 密鑰",
SubTitle: "從 Anthropic AI 獲取您的 API 密鑰",
Placeholder: "Anthropic API Key",
},
Endpoint: {
Title: "終端地址",
SubTitle: "示例:",
},
ApiVerion: {
Title: "API 版本 (claude api version)",
SubTitle: "選擇一個特定的 API 版本输入",
},
},
Google: {
ApiKey: {
Title: "API 密鑰",
@@ -467,12 +484,12 @@ const tw = {
type DeepPartial<T> = T extends object
? {
[P in keyof T]?: DeepPartial<T[P]>;
}
[P in keyof T]?: DeepPartial<T[P]>;
}
: T;
export type LocaleType = typeof tw;
export type PartialLocaleType = DeepPartial<typeof tw>;
export default tw;
// Translated by @chunkiuuu, feel free the submit new pr if there are typo/incorrect translations :D
// Translated by @chunkiuuu, feel free the submit new pr if there are typo/incorrect translations :D

View File

@@ -36,6 +36,11 @@ const DEFAULT_ACCESS_STATE = {
googleApiKey: "",
googleApiVersion: "v1",
// anthropic
anthropicApiKey: "",
anthropicApiVersion: "2023-06-01",
anthropicUrl: "",
// server config
needCode: true,
hideUserApiKey: false,
@@ -67,6 +72,10 @@ export const useAccessStore = createPersistStore(
return ensure(get(), ["googleApiKey"]);
},
isValidAnthropic() {
return ensure(get(), ["anthropicApiKey"]);
},
isAuthorized() {
this.fetch();
@@ -75,6 +84,7 @@ export const useAccessStore = createPersistStore(
this.isValidOpenAI() ||
this.isValidAzure() ||
this.isValidGoogle() ||
this.isValidAnthropic() ||
!this.enabledAccessControl() ||
(this.enabledAccessControl() && ensure(get(), ["accessCode"]))
);

View File

@@ -348,6 +348,8 @@ export const useChatStore = createPersistStore(
var api: ClientApi;
if (modelConfig.model.startsWith("gemini")) {
api = new ClientApi(ModelProvider.GeminiPro);
} else if (modelConfig.model.startsWith("claude")) {
api = new ClientApi(ModelProvider.Claude);
} else {
api = new ClientApi(ModelProvider.GPT);
}
@@ -494,7 +496,6 @@ export const useChatStore = createPersistStore(
tokenCount += estimateTokenLength(getMessageTextContent(msg));
reversedRecentMessages.push(msg);
}
// concat all messages
const recentMessages = [
...systemPrompts,
@@ -533,6 +534,8 @@ export const useChatStore = createPersistStore(
var api: ClientApi;
if (modelConfig.model.startsWith("gemini")) {
api = new ClientApi(ModelProvider.GeminiPro);
} else if (modelConfig.model.startsWith("claude")) {
api = new ClientApi(ModelProvider.Claude);
} else {
api = new ClientApi(ModelProvider.GPT);
}
@@ -557,6 +560,7 @@ export const useChatStore = createPersistStore(
messages: topicMessages,
config: {
model: getSummarizeModel(session.mask.modelConfig.model),
stream: false,
},
onFinish(message) {
get().updateCurrentSession(
@@ -600,6 +604,10 @@ export const useChatStore = createPersistStore(
historyMsgLength > modelConfig.compressMessageLengthThreshold &&
modelConfig.sendMemory
) {
/** Destruct max_tokens while summarizing
* this param is just shit
**/
const { max_tokens, ...modelcfg } = modelConfig;
api.llm.chat({
messages: toBeSummarizedMsgs.concat(
createMessage({
@@ -609,7 +617,7 @@ export const useChatStore = createPersistStore(
}),
),
config: {
...modelConfig,
...modelcfg,
stream: true,
model: getSummarizeModel(session.mask.modelConfig.model),
},

View File

@@ -1 +1,9 @@
export type Updater<T> = (updater: (value: T) => void) => void;
export const ROLES = ["system", "user", "assistant"] as const;
export type MessageRole = (typeof ROLES)[number];
export interface RequestMessage {
role: MessageRole;
content: string;
}

View File

@@ -2,16 +2,17 @@ import { useEffect, useState } from "react";
import { showToast } from "./components/ui-lib";
import Locale from "./locales";
import { RequestMessage } from "./client/api";
import { DEFAULT_MODELS } from "./constant";
export function trimTopic(topic: string) {
// Fix an issue where double quotes still show in the Indonesian language
// This will remove the specified punctuation from the end of the string
// and also trim quotes from both the start and end if they exist.
return topic
// fix for gemini
.replace(/^["“”*]+|["“”*]+$/g, "")
.replace(/[,。!?”“"、,.!?*]*$/, "");
return (
topic
// fix for gemini
.replace(/^["“”*]+|["“”*]+$/g, "")
.replace(/[,。!?”“"、,.!?*]*$/, "")
);
}
export async function copyToClipboard(text: string) {
@@ -57,10 +58,7 @@ export async function downloadAs(text: string, filename: string) {
if (result !== null) {
try {
await window.__TAURI__.fs.writeTextFile(
result,
text
);
await window.__TAURI__.fs.writeTextFile(result, text);
showToast(Locale.Download.Success);
} catch (error) {
showToast(Locale.Download.Failed);
@@ -293,10 +291,7 @@ export function getMessageImages(message: RequestMessage): string[] {
export function isVisionModel(model: string) {
// Note: This is a better way using the TypeScript feature instead of `&&` or `||` (ts v5.5.0-dev.20240314 I've been using)
const visionKeywords = [
"vision",
"claude-3",
];
const visionKeywords = ["vision", "claude-3"];
return visionKeywords.some(keyword => model.includes(keyword));
return visionKeywords.some((keyword) => model.includes(keyword));
}

View File

@@ -18,8 +18,15 @@ export function createWebDavClient(store: SyncStore) {
method: "MKCOL",
headers: this.headers(),
});
console.log("[WebDav] check", res.status, res.statusText);
return [201, 200, 404, 301, 302, 307, 308].includes(res.status);
const success = [201, 200, 404, 405, 301, 302, 307, 308].includes(
res.status,
);
console.log(
`[WebDav] check ${success ? "success" : "failed"}, ${res.status} ${
res.statusText
}`,
);
return success;
} catch (e) {
console.error("[WebDav] failed to check", e);
}

17
app/utils/object.ts Normal file
View File

@@ -0,0 +1,17 @@
export function omit<T extends object, U extends (keyof T)[]>(
obj: T,
...keys: U
): Omit<T, U[number]> {
const ret: any = { ...obj };
keys.forEach((key) => delete ret[key]);
return ret;
}
export function pick<T extends object, U extends (keyof T)[]>(
obj: T,
...keys: U
): Pick<T, U[number]> {
const ret: any = {};
keys.forEach((key) => (ret[key] = obj[key]));
return ret;
}

View File

@@ -77,6 +77,10 @@ if (mode !== "export") {
source: "/api/proxy/openai/:path*",
destination: "https://api.openai.com/:path*",
},
{
source: "/api/proxy/anthropic/:path*",
destination: "https://api.anthropic.com/:path*",
},
{
source: "/google-fonts/:path*",
destination: "https://fonts.googleapis.com/:path*",

View File

@@ -22,7 +22,7 @@
"@svgr/webpack": "^6.5.1",
"@vercel/analytics": "^0.1.11",
"@vercel/speed-insights": "^1.0.2",
"emoji-picker-react": "^4.5.15",
"emoji-picker-react": "^4.9.2",
"fuse.js": "^7.0.0",
"html-to-image": "^1.11.11",
"mermaid": "^10.6.1",
@@ -44,9 +44,9 @@
"zustand": "^4.3.8"
},
"devDependencies": {
"@tauri-apps/cli": "1.5.7",
"@types/node": "^20.9.0",
"@types/react": "^18.2.14",
"@tauri-apps/cli": "1.5.11",
"@types/node": "^20.11.30",
"@types/react": "^18.2.70",
"@types/react-dom": "^18.2.7",
"@types/react-katex": "^3.0.0",
"@types/spark-md5": "^3.0.4",
@@ -54,7 +54,7 @@
"eslint": "^8.49.0",
"eslint-config-next": "13.4.19",
"eslint-config-prettier": "^8.8.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-prettier": "^5.1.3",
"husky": "^8.0.0",
"lint-staged": "^13.2.2",
"prettier": "^3.0.2",

226
yarn.lock
View File

@@ -1303,17 +1303,10 @@
"@nodelib/fs.scandir" "2.1.5"
fastq "^1.6.0"
"@pkgr/utils@^2.3.1":
version "2.3.1"
resolved "https://registry.yarnpkg.com/@pkgr/utils/-/utils-2.3.1.tgz#0a9b06ffddee364d6642b3cd562ca76f55b34a03"
integrity sha512-wfzX8kc1PMyUILA+1Z/EqoE4UCXGy0iRGMhPwdfae1+f0OXlLqCk+By+aMzgJBzR9AzS4CDizioG6Ss1gvAFJw==
dependencies:
cross-spawn "^7.0.3"
is-glob "^4.0.3"
open "^8.4.0"
picocolors "^1.0.0"
tiny-glob "^0.2.9"
tslib "^2.4.0"
"@pkgr/core@^0.1.0":
version "0.1.0"
resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.1.0.tgz#7d8dacb7fdef0e4387caf7396cbd77f179867d06"
integrity sha512-Zwq5OCzuwJC2jwqmpEQt7Ds1DTi6BWSwoGkbb1n9pO3hzb35BoJELx7c0T23iDkBGkh2e7tvOtjF3tr3OaQHDQ==
"@remix-run/router@1.8.0":
version "1.8.0"
@@ -1438,71 +1431,71 @@
dependencies:
tslib "^2.4.0"
"@tauri-apps/cli-darwin-arm64@1.5.7":
version "1.5.7"
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-1.5.7.tgz#3435f1b6c4b431e0283f94c3a0bd486be66b24ee"
integrity sha512-eUpOUhs2IOpKaLa6RyGupP2owDLfd0q2FR/AILzryjtBtKJJRDQQvuotf+LcbEce2Nc2AHeYJIqYAsB4sw9K+g==
"@tauri-apps/cli-darwin-arm64@1.5.11":
version "1.5.11"
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-1.5.11.tgz#a831f98f685148e46e8050dbdddbf4bcdda9ddc6"
integrity sha512-2NLSglDb5VfvTbMtmOKWyD+oaL/e8Z/ZZGovHtUFyUSFRabdXc6cZOlcD1BhFvYkHqm+TqGaz5qtPR5UbqDs8A==
"@tauri-apps/cli-darwin-x64@1.5.7":
version "1.5.7"
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-1.5.7.tgz#d3d646e790067158d14a1f631a50c67dc05e3360"
integrity sha512-zfumTv1xUuR+RB1pzhRy+51tB6cm8I76g0xUBaXOfEdOJ9FqW5GW2jdnEUbpNuU65qJ1lB8LVWHKGrSWWKazew==
"@tauri-apps/cli-darwin-x64@1.5.11":
version "1.5.11"
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-1.5.11.tgz#0afae17fe1e84b9699a6b9824cd83b60c6ebfa59"
integrity sha512-/RQllHiJRH2fJOCudtZlaUIjofkHzP3zZgxi71ZUm7Fy80smU5TDfwpwOvB0wSVh0g/ciDjMArCSTo0MRvL+ag==
"@tauri-apps/cli-linux-arm-gnueabihf@1.5.7":
version "1.5.7"
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-1.5.7.tgz#049c12980cdfd67fe9e5163762bf77f3c85f6956"
integrity sha512-JngWNqS06bMND9PhiPWp0e+yknJJuSozsSbo+iMzHoJNRauBZCUx+HnUcygUR66Cy6qM4eJvLXtsRG7ApxvWmg==
"@tauri-apps/cli-linux-arm-gnueabihf@1.5.11":
version "1.5.11"
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-1.5.11.tgz#c46166d7f6c1022105a13d530b1d1336f628981f"
integrity sha512-IlBuBPKmMm+a5LLUEK6a21UGr9ZYd6zKuKLq6IGM4tVweQa8Sf2kP2Nqs74dMGIUrLmMs0vuqdURpykQg+z4NQ==
"@tauri-apps/cli-linux-arm64-gnu@1.5.7":
version "1.5.7"
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-1.5.7.tgz#d1c143da15cba74eebfaaf1662f0734e30f97562"
integrity sha512-WyIYP9BskgBGq+kf4cLAyru8ArrxGH2eMYGBJvuNEuSaqBhbV0i1uUxvyWdazllZLAEz1WvSocUmSwLknr1+sQ==
"@tauri-apps/cli-linux-arm64-gnu@1.5.11":
version "1.5.11"
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-1.5.11.tgz#fd5c539a03371e0ab6cd00563dced1610ceb8943"
integrity sha512-w+k1bNHCU/GbmXshtAhyTwqosThUDmCEFLU4Zkin1vl2fuAtQry2RN7thfcJFepblUGL/J7yh3Q/0+BCjtspKQ==
"@tauri-apps/cli-linux-arm64-musl@1.5.7":
version "1.5.7"
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.5.7.tgz#f79a17f5360a8ab25b90f3a8e9e6327d5378072f"
integrity sha512-OrDpihQP2MB0JY1a/wP9wsl9dDjFDpVEZOQxt4hU+UVGRCZQok7ghPBg4+Xpd1CkNkcCCuIeY8VxRvwLXpnIzg==
"@tauri-apps/cli-linux-arm64-musl@1.5.11":
version "1.5.11"
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.5.11.tgz#bf7f940c3aca981d7c240857a86568d5b6e8310f"
integrity sha512-PN6/dl+OfYQ/qrAy4HRAfksJ2AyWQYn2IA/2Wwpaa7SDRz2+hzwTQkvajuvy0sQ5L2WCG7ymFYRYMbpC6Hk9Pg==
"@tauri-apps/cli-linux-x64-gnu@1.5.7":
version "1.5.7"
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-1.5.7.tgz#2cbd17998dcfc8a465d61f30ac9e99ae65e2c2e8"
integrity sha512-4T7FAYVk76rZi8VkuLpiKUAqaSxlva86C1fHm/RtmoTKwZEV+MI3vIMoVg+AwhyWIy9PS55C75nF7+OwbnFnvQ==
"@tauri-apps/cli-linux-x64-gnu@1.5.11":
version "1.5.11"
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-1.5.11.tgz#17323105e3863a3f36d51771e642e489037ba59b"
integrity sha512-MTVXLi89Nj7Apcvjezw92m7ZqIDKT5SFKZtVPCg6RoLUBTzko/BQoXYIRWmdoz2pgkHDUHgO2OMJ8oKzzddXbw==
"@tauri-apps/cli-linux-x64-musl@1.5.7":
version "1.5.7"
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-1.5.7.tgz#d5d4ddded945cc781568d72b7eba367121f28525"
integrity sha512-LL9aMK601BmQjAUDcKWtt5KvAM0xXi0iJpOjoUD3LPfr5dLvBMTflVHQDAEtuZexLQyqpU09+60781PrI/FCTw==
"@tauri-apps/cli-linux-x64-musl@1.5.11":
version "1.5.11"
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-1.5.11.tgz#83e22026771ec8ab094922ab114a7385532aa16c"
integrity sha512-kwzAjqFpz7rvTs7WGZLy/a5nS5t15QKr3E9FG95MNF0exTl3d29YoAUAe1Mn0mOSrTJ9Z+vYYAcI/QdcsGBP+w==
"@tauri-apps/cli-win32-arm64-msvc@1.5.7":
version "1.5.7"
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-1.5.7.tgz#05a1bd4e2bc692bad995edb9d07e616cc5682fd5"
integrity sha512-TmAdM6GVkfir3AUFsDV2gyc25kIbJeAnwT72OnmJGAECHs/t/GLP9IkFLLVcFKsiosRf8BXhVyQ84NYkSWo14w==
"@tauri-apps/cli-win32-arm64-msvc@1.5.11":
version "1.5.11"
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-1.5.11.tgz#817874d230fdb09e7211013006a9a22f66ace573"
integrity sha512-L+5NZ/rHrSUrMxjj6YpFYCXp6wHnq8c8SfDTBOX8dO8x+5283/vftb4vvuGIsLS4UwUFXFnLt3XQr44n84E67Q==
"@tauri-apps/cli-win32-ia32-msvc@1.5.7":
version "1.5.7"
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-1.5.7.tgz#8c832f4dc88374255ef1cda4d2d6a6d61a921388"
integrity sha512-bqWfxwCfLmrfZy69sEU19KHm5TFEaMb8KIekd4aRq/kyOlrjKLdZxN1PyNRP8zpJA1lTiRHzfUDfhpmnZH/skg==
"@tauri-apps/cli-win32-ia32-msvc@1.5.11":
version "1.5.11"
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-1.5.11.tgz#dee1a00eb9e216415d9d6ab9386c35849613c560"
integrity sha512-oVlD9IVewrY0lZzTdb71kNXkjdgMqFq+ohb67YsJb4Rf7o8A9DTlFds1XLCe3joqLMm4M+gvBKD7YnGIdxQ9vA==
"@tauri-apps/cli-win32-x64-msvc@1.5.7":
version "1.5.7"
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-1.5.7.tgz#adfcce46f796dd22ef69fb26ad8c6972a3263985"
integrity sha512-OxLHVBNdzyQ//xT3kwjQFnJTn/N5zta/9fofAkXfnL7vqmVn6s/RY1LDa3sxCHlRaKw0n3ShpygRbM9M8+sO9w==
"@tauri-apps/cli-win32-x64-msvc@1.5.11":
version "1.5.11"
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-1.5.11.tgz#c003ce00b36d056a8b08e0ecf4633c2bba00c497"
integrity sha512-1CexcqUFCis5ypUIMOKllxUBrna09McbftWENgvVXMfA+SP+yPDPAVb8fIvUcdTIwR/yHJwcIucmTB4anww4vg==
"@tauri-apps/cli@1.5.7":
version "1.5.7"
resolved "https://registry.yarnpkg.com/@tauri-apps/cli/-/cli-1.5.7.tgz#8f9a8bf577a39b7f7c0e5b125e7b5b3e149cfb5a"
integrity sha512-z7nXLpDAYfQqR5pYhQlWOr88DgPq1AfQyxHhGiakiVgWlaG0ikEfQxop2txrd52H0TRADG0JHR9vFrVFPv4hVQ==
"@tauri-apps/cli@1.5.11":
version "1.5.11"
resolved "https://registry.yarnpkg.com/@tauri-apps/cli/-/cli-1.5.11.tgz#02beb559b3b55836c90a1ba9121b3fc50e3760cd"
integrity sha512-B475D7phZrq5sZ3kDABH4g2mEoUIHtnIO+r4ZGAAfsjMbZCwXxR/jlMGTEL+VO3YzjpF7gQe38IzB4vLBbVppw==
optionalDependencies:
"@tauri-apps/cli-darwin-arm64" "1.5.7"
"@tauri-apps/cli-darwin-x64" "1.5.7"
"@tauri-apps/cli-linux-arm-gnueabihf" "1.5.7"
"@tauri-apps/cli-linux-arm64-gnu" "1.5.7"
"@tauri-apps/cli-linux-arm64-musl" "1.5.7"
"@tauri-apps/cli-linux-x64-gnu" "1.5.7"
"@tauri-apps/cli-linux-x64-musl" "1.5.7"
"@tauri-apps/cli-win32-arm64-msvc" "1.5.7"
"@tauri-apps/cli-win32-ia32-msvc" "1.5.7"
"@tauri-apps/cli-win32-x64-msvc" "1.5.7"
"@tauri-apps/cli-darwin-arm64" "1.5.11"
"@tauri-apps/cli-darwin-x64" "1.5.11"
"@tauri-apps/cli-linux-arm-gnueabihf" "1.5.11"
"@tauri-apps/cli-linux-arm64-gnu" "1.5.11"
"@tauri-apps/cli-linux-arm64-musl" "1.5.11"
"@tauri-apps/cli-linux-x64-gnu" "1.5.11"
"@tauri-apps/cli-linux-x64-musl" "1.5.11"
"@tauri-apps/cli-win32-arm64-msvc" "1.5.11"
"@tauri-apps/cli-win32-ia32-msvc" "1.5.11"
"@tauri-apps/cli-win32-x64-msvc" "1.5.11"
"@trysound/sax@0.2.0":
version "0.2.0"
@@ -1601,10 +1594,10 @@
resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.31.tgz#31b7ca6407128a3d2bbc27fe2d21b345397f6197"
integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==
"@types/node@*", "@types/node@^20.9.0":
version "20.9.0"
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.9.0.tgz#bfcdc230583aeb891cf51e73cfdaacdd8deae298"
integrity sha512-nekiGu2NDb1BcVofVcEKMIwzlx4NjHlcjhoxxKBNLtz15Y1z7MYf549DFvkHSId02Ax6kGwWntIBPC3l/JZcmw==
"@types/node@*", "@types/node@^20.11.30":
version "20.11.30"
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.30.tgz#9c33467fc23167a347e73834f788f4b9f399d66f"
integrity sha512-dHM6ZxwlmuZaRmUPfv1p+KrdD1Dci04FbdEm/9wEMouFqxYoFl5aMkt0VMAUtYRQDyYvD41WJLukhq/ha3YuTw==
dependencies:
undici-types "~5.26.4"
@@ -1632,10 +1625,10 @@
dependencies:
"@types/react" "*"
"@types/react@*", "@types/react@^18.2.14":
version "18.2.14"
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.14.tgz#fa7a6fecf1ce35ca94e74874f70c56ce88f7a127"
integrity sha512-A0zjq+QN/O0Kpe30hA1GidzyFjatVvrpIvWLxD+xv67Vt91TWWgco9IvrJBkeyHm1trGaFS/FSGqPlhyeZRm0g==
"@types/react@*", "@types/react@^18.2.70":
version "18.2.70"
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.70.tgz#89a37f9e0a6a4931f4259c598f40fd44dd6abf71"
integrity sha512-hjlM2hho2vqklPhopNkXkdkeq6Lv8WSZTpr7956zY+3WS5cfYUewtCzsJLsbW5dEv3lfSeQ4W14ZFeKC437JRQ==
dependencies:
"@types/prop-types" "*"
"@types/scheduler" "*"
@@ -2752,11 +2745,6 @@ deepmerge@^4.2.2:
resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a"
integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==
define-lazy-prop@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f"
integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==
define-properties@^1.1.3, define-properties@^1.1.4:
version "1.2.0"
resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.0.tgz#52988570670c9eacedd8064f4a990f2405849bd5"
@@ -2858,10 +2846,12 @@ elkjs@^0.8.2:
resolved "https://registry.npmmirror.com/elkjs/-/elkjs-0.8.2.tgz#c37763c5a3e24e042e318455e0147c912a7c248e"
integrity sha512-L6uRgvZTH+4OF5NE/MBbzQx/WYpru1xCBE9respNj6qznEewGUIfhzmm7horWWxbNO2M0WckQypGctR8lH79xQ==
emoji-picker-react@^4.5.15:
version "4.5.15"
resolved "https://registry.yarnpkg.com/emoji-picker-react/-/emoji-picker-react-4.5.15.tgz#e12797c50584cb8af8aee7eb6c7c8fd953e41f7e"
integrity sha512-BTqo+pNUE8kqX8BKFTbD4fhlxcA69qfie5En4PerReLaaPfXVyRlDJ1uf85nKj2u5esUQ999iUf8YyqcPsM2Qw==
emoji-picker-react@^4.9.2:
version "4.9.2"
resolved "https://registry.yarnpkg.com/emoji-picker-react/-/emoji-picker-react-4.9.2.tgz#5118c5e1028ce4a96c94eb7c9bef09d30b08742c"
integrity sha512-pdvLKpto0DMrjE+/8V9QeYjrMcOkJmqBn3GyCSG2zanY32rN2cnWzBUmzArvapAjzBvgf7hNmJP8xmsdu0cmJA==
dependencies:
flairup "0.0.38"
emoji-regex@^8.0.0:
version "8.0.0"
@@ -3103,12 +3093,13 @@ eslint-plugin-jsx-a11y@^6.5.1:
object.fromentries "^2.0.6"
semver "^6.3.0"
eslint-plugin-prettier@^4.2.1:
version "4.2.1"
resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz#651cbb88b1dab98bfd42f017a12fa6b2d993f94b"
integrity sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==
eslint-plugin-prettier@^5.1.3:
version "5.1.3"
resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.3.tgz#17cfade9e732cef32b5f5be53bd4e07afd8e67e1"
integrity sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==
dependencies:
prettier-linter-helpers "^1.0.0"
synckit "^0.8.6"
"eslint-plugin-react-hooks@^4.5.0 || 5.0.0-canary-7118f5dd7-20230705":
version "4.6.0"
@@ -3338,6 +3329,11 @@ find-up@^5.0.0:
locate-path "^6.0.0"
path-exists "^4.0.0"
flairup@0.0.38:
version "0.0.38"
resolved "https://registry.yarnpkg.com/flairup/-/flairup-0.0.38.tgz#62216990a8317a1b07d1d816033624c5b2130f31"
integrity sha512-W9QA5TM7eYNlGoBYwfVn/o6v4yWBCxfq4+EJ5w774oFeyWvVWnYq6Dgt4CJltjG9y/lPwbOqz3jSSr8K66ToGg==
flat-cache@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11"
@@ -3499,11 +3495,6 @@ globalthis@^1.0.3:
dependencies:
define-properties "^1.1.3"
globalyzer@0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/globalyzer/-/globalyzer-0.1.0.tgz#cb76da79555669a1519d5a8edf093afaa0bf1465"
integrity sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==
globby@^11.1.0:
version "11.1.0"
resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b"
@@ -3527,11 +3518,6 @@ globby@^13.1.3:
merge2 "^1.4.1"
slash "^4.0.0"
globrex@^0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/globrex/-/globrex-0.1.2.tgz#dd5d9ec826232730cd6793a5e33a9302985e6098"
integrity sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==
gopd@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c"
@@ -3850,11 +3836,6 @@ is-date-object@^1.0.1, is-date-object@^1.0.5:
dependencies:
has-tostringtag "^1.0.0"
is-docker@^2.0.0, is-docker@^2.1.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa"
integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==
is-extglob@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
@@ -3979,13 +3960,6 @@ is-weakset@^2.0.1:
call-bind "^1.0.2"
get-intrinsic "^1.1.1"
is-wsl@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271"
integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==
dependencies:
is-docker "^2.0.0"
isarray@^2.0.5:
version "2.0.5"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723"
@@ -4960,15 +4934,6 @@ onetime@^6.0.0:
dependencies:
mimic-fn "^4.0.0"
open@^8.4.0:
version "8.4.2"
resolved "https://registry.yarnpkg.com/open/-/open-8.4.2.tgz#5b5ffe2a8f793dcd2aad73e550cb87b59cb084f9"
integrity sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==
dependencies:
define-lazy-prop "^2.0.0"
is-docker "^2.1.1"
is-wsl "^2.2.0"
optionator@^0.9.3:
version "0.9.3"
resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.3.tgz#007397d44ed1872fdc6ed31360190f81814e2c64"
@@ -5748,13 +5713,13 @@ svgo@^2.8.0:
picocolors "^1.0.0"
stable "^0.1.8"
synckit@^0.8.5:
version "0.8.5"
resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.8.5.tgz#b7f4358f9bb559437f9f167eb6bc46b3c9818fa3"
integrity sha512-L1dapNV6vu2s/4Sputv8xGsCdAVlb5nRDMFU/E27D44l5U6cw1g0dGd45uLc+OXjNMmF4ntiMdCimzcjFKQI8Q==
synckit@^0.8.5, synckit@^0.8.6:
version "0.8.8"
resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.8.8.tgz#fe7fe446518e3d3d49f5e429f443cf08b6edfcd7"
integrity sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==
dependencies:
"@pkgr/utils" "^2.3.1"
tslib "^2.5.0"
"@pkgr/core" "^0.1.0"
tslib "^2.6.2"
tapable@^2.1.1, tapable@^2.2.0:
version "2.2.1"
@@ -5797,14 +5762,6 @@ through@^2.3.8:
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==
tiny-glob@^0.2.9:
version "0.2.9"
resolved "https://registry.yarnpkg.com/tiny-glob/-/tiny-glob-0.2.9.tgz#2212d441ac17928033b110f8b3640683129d31e2"
integrity sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==
dependencies:
globalyzer "0.1.0"
globrex "^0.1.2"
tiny-invariant@^1.0.6:
version "1.3.1"
resolved "https://registry.npmmirror.com/tiny-invariant/-/tiny-invariant-1.3.1.tgz#8560808c916ef02ecfd55e66090df23a4b7aa642"
@@ -5852,11 +5809,16 @@ tsconfig-paths@^3.14.1:
minimist "^1.2.6"
strip-bom "^3.0.0"
tslib@^2.1.0, tslib@^2.4.0, tslib@^2.5.0:
tslib@^2.1.0, tslib@^2.4.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.0.tgz#42bfed86f5787aeb41d031866c8f402429e0fddf"
integrity sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==
tslib@^2.6.2:
version "2.6.2"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae"
integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==
type-check@^0.4.0, type-check@~0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1"