Merge branch 'ChatGPTNextWeb:main' into personal
This commit is contained in:
commit
9643a34344
|
@ -66,4 +66,4 @@ ANTHROPIC_API_VERSION=
|
||||||
ANTHROPIC_URL=
|
ANTHROPIC_URL=
|
||||||
|
|
||||||
### (optional)
|
### (optional)
|
||||||
WHITE_WEBDEV_ENDPOINTS=
|
WHITE_WEBDAV_ENDPOINTS=
|
|
@ -1,4 +1,7 @@
|
||||||
{
|
{
|
||||||
"extends": "next/core-web-vitals",
|
"extends": "next/core-web-vitals",
|
||||||
"plugins": ["prettier"]
|
"plugins": ["prettier", "unused-imports"],
|
||||||
|
"rules": {
|
||||||
|
"unused-imports/no-unused-imports": "warn"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,7 +49,7 @@ jobs:
|
||||||
run: npm install --global vercel@latest
|
run: npm install --global vercel@latest
|
||||||
|
|
||||||
- name: Cache dependencies
|
- name: Cache dependencies
|
||||||
uses: actions/cache@v2
|
uses: actions/cache@v4
|
||||||
id: cache-npm
|
id: cache-npm
|
||||||
with:
|
with:
|
||||||
path: ~/.npm
|
path: ~/.npm
|
||||||
|
|
11
README.md
11
README.md
|
@ -12,15 +12,18 @@ One-Click to get a well-designed cross-platform ChatGPT web UI, with GPT3, GPT4
|
||||||
|
|
||||||
一键免费部署你的跨平台私人 ChatGPT 应用, 支持 GPT3, GPT4 & Gemini Pro 模型。
|
一键免费部署你的跨平台私人 ChatGPT 应用, 支持 GPT3, GPT4 & Gemini Pro 模型。
|
||||||
|
|
||||||
|
[![Saas][Saas-image]][saas-url]
|
||||||
[![Web][Web-image]][web-url]
|
[![Web][Web-image]][web-url]
|
||||||
[![Windows][Windows-image]][download-url]
|
[![Windows][Windows-image]][download-url]
|
||||||
[![MacOS][MacOS-image]][download-url]
|
[![MacOS][MacOS-image]][download-url]
|
||||||
[![Linux][Linux-image]][download-url]
|
[![Linux][Linux-image]][download-url]
|
||||||
|
|
||||||
[Web App](https://app.nextchat.dev/) / [Desktop App](https://github.com/Yidadaa/ChatGPT-Next-Web/releases) / [Discord](https://discord.gg/YCkeafCafC) / [Enterprise Edition](#enterprise-edition) / [Twitter](https://twitter.com/NextChatDev)
|
[NextChatAI](https://nextchat.dev/chat?utm_source=readme) / [Web App](https://app.nextchat.dev) / [Desktop App](https://github.com/Yidadaa/ChatGPT-Next-Web/releases) / [Discord](https://discord.gg/YCkeafCafC) / [Enterprise Edition](#enterprise-edition) / [Twitter](https://twitter.com/NextChatDev)
|
||||||
|
|
||||||
[网页版](https://app.nextchat.dev/) / [客户端](https://github.com/Yidadaa/ChatGPT-Next-Web/releases) / [企业版](#%E4%BC%81%E4%B8%9A%E7%89%88) / [反馈](https://github.com/Yidadaa/ChatGPT-Next-Web/issues)
|
[NextChatAI](https://nextchat.dev/chat) / [网页版](https://app.nextchat.dev) / [客户端](https://github.com/Yidadaa/ChatGPT-Next-Web/releases) / [企业版](#%E4%BC%81%E4%B8%9A%E7%89%88) / [反馈](https://github.com/Yidadaa/ChatGPT-Next-Web/issues)
|
||||||
|
|
||||||
|
[saas-url]: https://nextchat.dev/chat?utm_source=readme
|
||||||
|
[saas-image]: https://img.shields.io/badge/NextChat-Saas-green?logo=microsoftedge
|
||||||
[web-url]: https://app.nextchat.dev/
|
[web-url]: https://app.nextchat.dev/
|
||||||
[download-url]: https://github.com/Yidadaa/ChatGPT-Next-Web/releases
|
[download-url]: https://github.com/Yidadaa/ChatGPT-Next-Web/releases
|
||||||
[Web-image]: https://img.shields.io/badge/Web-PWA-orange?logo=microsoftedge
|
[Web-image]: https://img.shields.io/badge/Web-PWA-orange?logo=microsoftedge
|
||||||
|
@ -172,7 +175,7 @@ We recommend that you follow the steps below to re-deploy:
|
||||||
|
|
||||||
### Enable Automatic Updates
|
### Enable Automatic Updates
|
||||||
|
|
||||||
> If you encounter a failure of Upstream Sync execution, please manually sync fork once.
|
> If you encounter a failure of Upstream Sync execution, please [manually update code](./README.md#manually-updating-code).
|
||||||
|
|
||||||
After forking the project, due to the limitations imposed by GitHub, you need to manually enable Workflows and Upstream Sync Action on the Actions page of the forked project. Once enabled, automatic updates will be scheduled every hour:
|
After forking the project, due to the limitations imposed by GitHub, you need to manually enable Workflows and Upstream Sync Action on the Actions page of the forked project. Once enabled, automatic updates will be scheduled every hour:
|
||||||
|
|
||||||
|
@ -340,7 +343,7 @@ For ByteDance: use `modelName@bytedance=deploymentName` to customize model name
|
||||||
|
|
||||||
Change default model
|
Change default model
|
||||||
|
|
||||||
### `WHITE_WEBDEV_ENDPOINTS` (optional)
|
### `WHITE_WEBDAV_ENDPOINTS` (optional)
|
||||||
|
|
||||||
You can use this option if you want to increase the number of webdav service addresses you are allowed to access, as required by the format:
|
You can use this option if you want to increase the number of webdav service addresses you are allowed to access, as required by the format:
|
||||||
- Each address must be a complete endpoint
|
- Each address must be a complete endpoint
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
一键免费部署你的私人 ChatGPT 网页应用,支持 GPT3, GPT4 & Gemini Pro 模型。
|
一键免费部署你的私人 ChatGPT 网页应用,支持 GPT3, GPT4 & Gemini Pro 模型。
|
||||||
|
|
||||||
[企业版](#%E4%BC%81%E4%B8%9A%E7%89%88) /[演示 Demo](https://chat-gpt-next-web.vercel.app/) / [反馈 Issues](https://github.com/Yidadaa/ChatGPT-Next-Web/issues) / [加入 Discord](https://discord.gg/zrhvHCr79N)
|
[NextChatAI](https://nextchat.dev/chat?utm_source=readme) / [企业版](#%E4%BC%81%E4%B8%9A%E7%89%88) / [演示 Demo](https://chat-gpt-next-web.vercel.app/) / [反馈 Issues](https://github.com/Yidadaa/ChatGPT-Next-Web/issues) / [加入 Discord](https://discord.gg/zrhvHCr79N)
|
||||||
|
|
||||||
[<img src="https://vercel.com/button" alt="Deploy on Zeabur" height="30">](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FChatGPTNextWeb%2FChatGPT-Next-Web&env=OPENAI_API_KEY&env=CODE&project-name=nextchat&repository-name=NextChat) [<img src="https://zeabur.com/button.svg" alt="Deploy on Zeabur" height="30">](https://zeabur.com/templates/ZBUEFA) [<img src="https://gitpod.io/button/open-in-gitpod.svg" alt="Open in Gitpod" height="30">](https://gitpod.io/#https://github.com/Yidadaa/ChatGPT-Next-Web)
|
[<img src="https://vercel.com/button" alt="Deploy on Zeabur" height="30">](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FChatGPTNextWeb%2FChatGPT-Next-Web&env=OPENAI_API_KEY&env=CODE&project-name=nextchat&repository-name=NextChat) [<img src="https://zeabur.com/button.svg" alt="Deploy on Zeabur" height="30">](https://zeabur.com/templates/ZBUEFA) [<img src="https://gitpod.io/button/open-in-gitpod.svg" alt="Open in Gitpod" height="30">](https://gitpod.io/#https://github.com/Yidadaa/ChatGPT-Next-Web)
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@
|
||||||
|
|
||||||
### 打开自动更新
|
### 打开自动更新
|
||||||
|
|
||||||
> 如果你遇到了 Upstream Sync 执行错误,请手动 Sync Fork 一次!
|
> 如果你遇到了 Upstream Sync 执行错误,请[手动 Sync Fork 一次](./README_CN.md#手动更新代码)!
|
||||||
|
|
||||||
当你 fork 项目之后,由于 Github 的限制,需要手动去你 fork 后的项目的 Actions 页面启用 Workflows,并启用 Upstream Sync Action,启用之后即可开启每小时定时自动更新:
|
当你 fork 项目之后,由于 Github 的限制,需要手动去你 fork 后的项目的 Actions 页面启用 Workflows,并启用 Upstream Sync Action,启用之后即可开启每小时定时自动更新:
|
||||||
|
|
||||||
|
@ -202,7 +202,7 @@ ByteDance Api Url.
|
||||||
|
|
||||||
如果你想禁用从链接解析预制设置,将此环境变量设置为 1 即可。
|
如果你想禁用从链接解析预制设置,将此环境变量设置为 1 即可。
|
||||||
|
|
||||||
### `WHITE_WEBDEV_ENDPOINTS` (可选)
|
### `WHITE_WEBDAV_ENDPOINTS` (可选)
|
||||||
|
|
||||||
如果你想增加允许访问的webdav服务地址,可以使用该选项,格式要求:
|
如果你想增加允许访问的webdav服务地址,可以使用该选项,格式要求:
|
||||||
- 每一个地址必须是一个完整的 endpoint
|
- 每一个地址必须是一个完整的 endpoint
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
ワンクリックで無料であなた専用の ChatGPT ウェブアプリをデプロイ。GPT3、GPT4 & Gemini Pro モデルをサポート。
|
ワンクリックで無料であなた専用の ChatGPT ウェブアプリをデプロイ。GPT3、GPT4 & Gemini Pro モデルをサポート。
|
||||||
|
|
||||||
[企業版](#企業版) / [デモ](https://chat-gpt-next-web.vercel.app/) / [フィードバック](https://github.com/Yidadaa/ChatGPT-Next-Web/issues) / [Discordに参加](https://discord.gg/zrhvHCr79N)
|
[NextChatAI](https://nextchat.dev/chat?utm_source=readme) / [企業版](#企業版) / [デモ](https://chat-gpt-next-web.vercel.app/) / [フィードバック](https://github.com/Yidadaa/ChatGPT-Next-Web/issues) / [Discordに参加](https://discord.gg/zrhvHCr79N)
|
||||||
|
|
||||||
[<img src="https://vercel.com/button" alt="Zeaburでデプロイ" height="30">](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FChatGPTNextWeb%2FChatGPT-Next-Web&env=OPENAI_API_KEY&env=CODE&project-name=nextchat&repository-name=NextChat) [<img src="https://zeabur.com/button.svg" alt="Zeaburでデプロイ" height="30">](https://zeabur.com/templates/ZBUEFA) [<img src="https://gitpod.io/button/open-in-gitpod.svg" alt="Gitpodで開く" height="30">](https://gitpod.io/#https://github.com/Yidadaa/ChatGPT-Next-Web)
|
[<img src="https://vercel.com/button" alt="Zeaburでデプロイ" height="30">](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FChatGPTNextWeb%2FChatGPT-Next-Web&env=OPENAI_API_KEY&env=CODE&project-name=nextchat&repository-name=NextChat) [<img src="https://zeabur.com/button.svg" alt="Zeaburでデプロイ" height="30">](https://zeabur.com/templates/ZBUEFA) [<img src="https://gitpod.io/button/open-in-gitpod.svg" alt="Gitpodで開く" height="30">](https://gitpod.io/#https://github.com/Yidadaa/ChatGPT-Next-Web)
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@
|
||||||
|
|
||||||
### 自動更新を開く
|
### 自動更新を開く
|
||||||
|
|
||||||
> Upstream Sync の実行エラーが発生した場合は、手動で Sync Fork してください!
|
> Upstream Sync の実行エラーが発生した場合は、[手動で Sync Fork](./README_JA.md#手動でコードを更新する) してください!
|
||||||
|
|
||||||
プロジェクトを fork した後、GitHub の制限により、fork 後のプロジェクトの Actions ページで Workflows を手動で有効にし、Upstream Sync Action を有効にする必要があります。有効化後、毎時の定期自動更新が可能になります:
|
プロジェクトを fork した後、GitHub の制限により、fork 後のプロジェクトの Actions ページで Workflows を手動で有効にし、Upstream Sync Action を有効にする必要があります。有効化後、毎時の定期自動更新が可能になります:
|
||||||
|
|
||||||
|
@ -193,7 +193,7 @@ ByteDance API の URL。
|
||||||
|
|
||||||
リンクからのプリセット設定解析を無効にしたい場合は、この環境変数を 1 に設定します。
|
リンクからのプリセット設定解析を無効にしたい場合は、この環境変数を 1 に設定します。
|
||||||
|
|
||||||
### `WHITE_WEBDEV_ENDPOINTS` (オプション)
|
### `WHITE_WEBDAV_ENDPOINTS` (オプション)
|
||||||
|
|
||||||
アクセス許可を与える WebDAV サービスのアドレスを追加したい場合、このオプションを使用します。フォーマット要件:
|
アクセス許可を与える WebDAV サービスのアドレスを追加したい場合、このオプションを使用します。フォーマット要件:
|
||||||
- 各アドレスは完全なエンドポイントでなければなりません。
|
- 各アドレスは完全なエンドポイントでなければなりません。
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { ApiPath } from "@/app/constant";
|
import { ApiPath } from "@/app/constant";
|
||||||
import { NextRequest, NextResponse } from "next/server";
|
import { NextRequest } from "next/server";
|
||||||
import { handle as openaiHandler } from "../../openai";
|
import { handle as openaiHandler } from "../../openai";
|
||||||
import { handle as azureHandler } from "../../azure";
|
import { handle as azureHandler } from "../../azure";
|
||||||
import { handle as googleHandler } from "../../google";
|
import { handle as googleHandler } from "../../google";
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import { getServerSideConfig } from "@/app/config/server";
|
import { getServerSideConfig } from "@/app/config/server";
|
||||||
import {
|
import {
|
||||||
Alibaba,
|
|
||||||
ALIBABA_BASE_URL,
|
ALIBABA_BASE_URL,
|
||||||
ApiPath,
|
ApiPath,
|
||||||
ModelProvider,
|
ModelProvider,
|
||||||
|
@ -10,7 +9,6 @@ import { prettyObject } from "@/app/utils/format";
|
||||||
import { NextRequest, NextResponse } from "next/server";
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
import { auth } from "@/app/api/auth";
|
import { auth } from "@/app/api/auth";
|
||||||
import { isModelAvailableInServer } from "@/app/utils/model";
|
import { isModelAvailableInServer } from "@/app/utils/model";
|
||||||
import type { RequestPayload } from "@/app/client/platforms/openai";
|
|
||||||
|
|
||||||
const serverConfig = getServerSideConfig();
|
const serverConfig = getServerSideConfig();
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,6 @@ import {
|
||||||
ANTHROPIC_BASE_URL,
|
ANTHROPIC_BASE_URL,
|
||||||
Anthropic,
|
Anthropic,
|
||||||
ApiPath,
|
ApiPath,
|
||||||
DEFAULT_MODELS,
|
|
||||||
ServiceProvider,
|
ServiceProvider,
|
||||||
ModelProvider,
|
ModelProvider,
|
||||||
} from "@/app/constant";
|
} from "@/app/constant";
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { getServerSideConfig } from "@/app/config/server";
|
|
||||||
import { ModelProvider } from "@/app/constant";
|
import { ModelProvider } from "@/app/constant";
|
||||||
import { prettyObject } from "@/app/utils/format";
|
import { prettyObject } from "@/app/utils/format";
|
||||||
import { NextRequest, NextResponse } from "next/server";
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
|
|
@ -3,7 +3,6 @@ import {
|
||||||
BAIDU_BASE_URL,
|
BAIDU_BASE_URL,
|
||||||
ApiPath,
|
ApiPath,
|
||||||
ModelProvider,
|
ModelProvider,
|
||||||
BAIDU_OATUH_URL,
|
|
||||||
ServiceProvider,
|
ServiceProvider,
|
||||||
} from "@/app/constant";
|
} from "@/app/constant";
|
||||||
import { prettyObject } from "@/app/utils/format";
|
import { prettyObject } from "@/app/utils/format";
|
||||||
|
|
|
@ -1,11 +1,6 @@
|
||||||
import { NextRequest, NextResponse } from "next/server";
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
import { getServerSideConfig } from "../config/server";
|
import { getServerSideConfig } from "../config/server";
|
||||||
import {
|
import { OPENAI_BASE_URL, ServiceProvider } from "../constant";
|
||||||
DEFAULT_MODELS,
|
|
||||||
OPENAI_BASE_URL,
|
|
||||||
GEMINI_BASE_URL,
|
|
||||||
ServiceProvider,
|
|
||||||
} from "../constant";
|
|
||||||
import { isModelAvailableInServer } from "../utils/model";
|
import { isModelAvailableInServer } from "../utils/model";
|
||||||
import { cloudflareAIGatewayUrl } from "../utils/cloudflare";
|
import { cloudflareAIGatewayUrl } from "../utils/cloudflare";
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,7 @@
|
||||||
import { NextRequest, NextResponse } from "next/server";
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
import { auth } from "./auth";
|
import { auth } from "./auth";
|
||||||
import { getServerSideConfig } from "@/app/config/server";
|
import { getServerSideConfig } from "@/app/config/server";
|
||||||
import {
|
import { ApiPath, GEMINI_BASE_URL, ModelProvider } from "@/app/constant";
|
||||||
ApiPath,
|
|
||||||
GEMINI_BASE_URL,
|
|
||||||
Google,
|
|
||||||
ModelProvider,
|
|
||||||
} from "@/app/constant";
|
|
||||||
import { prettyObject } from "@/app/utils/format";
|
import { prettyObject } from "@/app/utils/format";
|
||||||
|
|
||||||
const serverConfig = getServerSideConfig();
|
const serverConfig = getServerSideConfig();
|
||||||
|
@ -28,7 +23,8 @@ export async function handle(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const bearToken = req.headers.get("Authorization") ?? "";
|
const bearToken =
|
||||||
|
req.headers.get("x-goog-api-key") || req.headers.get("Authorization") || "";
|
||||||
const token = bearToken.trim().replaceAll("Bearer ", "").trim();
|
const token = bearToken.trim().replaceAll("Bearer ", "").trim();
|
||||||
|
|
||||||
const apiKey = token ? token : serverConfig.googleApiKey;
|
const apiKey = token ? token : serverConfig.googleApiKey;
|
||||||
|
@ -96,8 +92,8 @@ async function request(req: NextRequest, apiKey: string) {
|
||||||
},
|
},
|
||||||
10 * 60 * 1000,
|
10 * 60 * 1000,
|
||||||
);
|
);
|
||||||
const fetchUrl = `${baseUrl}${path}?key=${apiKey}${
|
const fetchUrl = `${baseUrl}${path}${
|
||||||
req?.nextUrl?.searchParams?.get("alt") === "sse" ? "&alt=sse" : ""
|
req?.nextUrl?.searchParams?.get("alt") === "sse" ? "?alt=sse" : ""
|
||||||
}`;
|
}`;
|
||||||
|
|
||||||
console.log("[Fetch Url] ", fetchUrl);
|
console.log("[Fetch Url] ", fetchUrl);
|
||||||
|
@ -105,6 +101,9 @@ async function request(req: NextRequest, apiKey: string) {
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
"Cache-Control": "no-store",
|
"Cache-Control": "no-store",
|
||||||
|
"x-goog-api-key":
|
||||||
|
req.headers.get("x-goog-api-key") ||
|
||||||
|
(req.headers.get("Authorization") ?? "").replace("Bearer ", ""),
|
||||||
},
|
},
|
||||||
method: req.method,
|
method: req.method,
|
||||||
body: req.body,
|
body: req.body,
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import { getServerSideConfig } from "@/app/config/server";
|
import { getServerSideConfig } from "@/app/config/server";
|
||||||
import {
|
import {
|
||||||
Iflytek,
|
|
||||||
IFLYTEK_BASE_URL,
|
IFLYTEK_BASE_URL,
|
||||||
ApiPath,
|
ApiPath,
|
||||||
ModelProvider,
|
ModelProvider,
|
||||||
|
@ -10,7 +9,6 @@ import { prettyObject } from "@/app/utils/format";
|
||||||
import { NextRequest, NextResponse } from "next/server";
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
import { auth } from "@/app/api/auth";
|
import { auth } from "@/app/api/auth";
|
||||||
import { isModelAvailableInServer } from "@/app/utils/model";
|
import { isModelAvailableInServer } from "@/app/utils/model";
|
||||||
import type { RequestPayload } from "@/app/client/platforms/openai";
|
|
||||||
// iflytek
|
// iflytek
|
||||||
|
|
||||||
const serverConfig = getServerSideConfig();
|
const serverConfig = getServerSideConfig();
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import { getServerSideConfig } from "@/app/config/server";
|
import { getServerSideConfig } from "@/app/config/server";
|
||||||
import {
|
import {
|
||||||
Moonshot,
|
|
||||||
MOONSHOT_BASE_URL,
|
MOONSHOT_BASE_URL,
|
||||||
ApiPath,
|
ApiPath,
|
||||||
ModelProvider,
|
ModelProvider,
|
||||||
|
@ -10,7 +9,6 @@ import { prettyObject } from "@/app/utils/format";
|
||||||
import { NextRequest, NextResponse } from "next/server";
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
import { auth } from "@/app/api/auth";
|
import { auth } from "@/app/api/auth";
|
||||||
import { isModelAvailableInServer } from "@/app/utils/model";
|
import { isModelAvailableInServer } from "@/app/utils/model";
|
||||||
import type { RequestPayload } from "@/app/client/platforms/openai";
|
|
||||||
|
|
||||||
const serverConfig = getServerSideConfig();
|
const serverConfig = getServerSideConfig();
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,8 @@
|
||||||
import { getServerSideConfig } from "@/app/config/server";
|
import { getServerSideConfig } from "@/app/config/server";
|
||||||
import {
|
import { TENCENT_BASE_URL, ModelProvider } from "@/app/constant";
|
||||||
TENCENT_BASE_URL,
|
|
||||||
ApiPath,
|
|
||||||
ModelProvider,
|
|
||||||
ServiceProvider,
|
|
||||||
Tencent,
|
|
||||||
} from "@/app/constant";
|
|
||||||
import { prettyObject } from "@/app/utils/format";
|
import { prettyObject } from "@/app/utils/format";
|
||||||
import { NextRequest, NextResponse } from "next/server";
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
import { auth } from "@/app/api/auth";
|
import { auth } from "@/app/api/auth";
|
||||||
import { isModelAvailableInServer } from "@/app/utils/model";
|
|
||||||
import { getHeader } from "@/app/utils/tencent";
|
import { getHeader } from "@/app/utils/tencent";
|
||||||
|
|
||||||
const serverConfig = getServerSideConfig();
|
const serverConfig = getServerSideConfig();
|
||||||
|
|
|
@ -6,7 +6,7 @@ const config = getServerSideConfig();
|
||||||
|
|
||||||
const mergedAllowedWebDavEndpoints = [
|
const mergedAllowedWebDavEndpoints = [
|
||||||
...internalAllowedWebDavEndpoints,
|
...internalAllowedWebDavEndpoints,
|
||||||
...config.allowedWebDevEndpoints,
|
...config.allowedWebDavEndpoints,
|
||||||
].filter((domain) => Boolean(domain.trim()));
|
].filter((domain) => Boolean(domain.trim()));
|
||||||
|
|
||||||
const normalizeUrl = (url: string) => {
|
const normalizeUrl = (url: string) => {
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import { getClientConfig } from "../config/client";
|
import { getClientConfig } from "../config/client";
|
||||||
import {
|
import {
|
||||||
ACCESS_CODE_PREFIX,
|
ACCESS_CODE_PREFIX,
|
||||||
Azure,
|
|
||||||
ModelProvider,
|
ModelProvider,
|
||||||
ServiceProvider,
|
ServiceProvider,
|
||||||
} from "../constant";
|
} from "../constant";
|
||||||
|
@ -26,6 +25,7 @@ export const ROLES = ["system", "user", "assistant"] as const;
|
||||||
export type MessageRole = (typeof ROLES)[number];
|
export type MessageRole = (typeof ROLES)[number];
|
||||||
|
|
||||||
export const Models = ["gpt-3.5-turbo", "gpt-4"] as const;
|
export const Models = ["gpt-3.5-turbo", "gpt-4"] as const;
|
||||||
|
export const TTSModels = ["tts-1", "tts-1-hd"] as const;
|
||||||
export type ChatModel = ModelType;
|
export type ChatModel = ModelType;
|
||||||
|
|
||||||
export interface MultimodalContent {
|
export interface MultimodalContent {
|
||||||
|
@ -54,6 +54,15 @@ export interface LLMConfig {
|
||||||
style?: DalleRequestPayload["style"];
|
style?: DalleRequestPayload["style"];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SpeechOptions {
|
||||||
|
model: string;
|
||||||
|
input: string;
|
||||||
|
voice: string;
|
||||||
|
response_format?: string;
|
||||||
|
speed?: number;
|
||||||
|
onController?: (controller: AbortController) => void;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ChatOptions {
|
export interface ChatOptions {
|
||||||
messages: RequestMessage[];
|
messages: RequestMessage[];
|
||||||
config: LLMConfig;
|
config: LLMConfig;
|
||||||
|
@ -88,6 +97,7 @@ export interface LLMModelProvider {
|
||||||
|
|
||||||
export abstract class LLMApi {
|
export abstract class LLMApi {
|
||||||
abstract chat(options: ChatOptions): Promise<void>;
|
abstract chat(options: ChatOptions): Promise<void>;
|
||||||
|
abstract speech(options: SpeechOptions): Promise<ArrayBuffer>;
|
||||||
abstract usage(): Promise<LLMUsage>;
|
abstract usage(): Promise<LLMUsage>;
|
||||||
abstract models(): Promise<LLMModel[]>;
|
abstract models(): Promise<LLMModel[]>;
|
||||||
}
|
}
|
||||||
|
@ -206,19 +216,22 @@ export function validString(x: string): boolean {
|
||||||
return x?.length > 0;
|
return x?.length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getHeaders() {
|
export function getHeaders(ignoreHeaders: boolean = false) {
|
||||||
const accessStore = useAccessStore.getState();
|
const accessStore = useAccessStore.getState();
|
||||||
const chatStore = useChatStore.getState();
|
const chatStore = useChatStore.getState();
|
||||||
const headers: Record<string, string> = {
|
let headers: Record<string, string> = {};
|
||||||
"Content-Type": "application/json",
|
if (!ignoreHeaders) {
|
||||||
Accept: "application/json",
|
headers = {
|
||||||
};
|
"Content-Type": "application/json",
|
||||||
|
Accept: "application/json",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const clientConfig = getClientConfig();
|
const clientConfig = getClientConfig();
|
||||||
|
|
||||||
function getConfig() {
|
function getConfig() {
|
||||||
const modelConfig = chatStore.currentSession().mask.modelConfig;
|
const modelConfig = chatStore.currentSession().mask.modelConfig;
|
||||||
const isGoogle = modelConfig.providerName == ServiceProvider.Google;
|
const isGoogle = modelConfig.providerName === ServiceProvider.Google;
|
||||||
const isAzure = modelConfig.providerName === ServiceProvider.Azure;
|
const isAzure = modelConfig.providerName === ServiceProvider.Azure;
|
||||||
const isAnthropic = modelConfig.providerName === ServiceProvider.Anthropic;
|
const isAnthropic = modelConfig.providerName === ServiceProvider.Anthropic;
|
||||||
const isBaidu = modelConfig.providerName == ServiceProvider.Baidu;
|
const isBaidu = modelConfig.providerName == ServiceProvider.Baidu;
|
||||||
|
@ -259,7 +272,13 @@ export function getHeaders() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAuthHeader(): string {
|
function getAuthHeader(): string {
|
||||||
return isAzure ? "api-key" : isAnthropic ? "x-api-key" : "Authorization";
|
return isAzure
|
||||||
|
? "api-key"
|
||||||
|
: isAnthropic
|
||||||
|
? "x-api-key"
|
||||||
|
: isGoogle
|
||||||
|
? "x-goog-api-key"
|
||||||
|
: "Authorization";
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
@ -270,14 +289,15 @@ export function getHeaders() {
|
||||||
apiKey,
|
apiKey,
|
||||||
isEnabledAccessControl,
|
isEnabledAccessControl,
|
||||||
} = getConfig();
|
} = getConfig();
|
||||||
// when using google api in app, not set auth header
|
|
||||||
if (isGoogle && clientConfig?.isApp) return headers;
|
|
||||||
// when using baidu api in app, not set auth header
|
// when using baidu api in app, not set auth header
|
||||||
if (isBaidu && clientConfig?.isApp) return headers;
|
if (isBaidu && clientConfig?.isApp) return headers;
|
||||||
|
|
||||||
const authHeader = getAuthHeader();
|
const authHeader = getAuthHeader();
|
||||||
|
|
||||||
const bearerToken = getBearerToken(apiKey, isAzure || isAnthropic);
|
const bearerToken = getBearerToken(
|
||||||
|
apiKey,
|
||||||
|
isAzure || isAnthropic || isGoogle,
|
||||||
|
);
|
||||||
|
|
||||||
if (bearerToken) {
|
if (bearerToken) {
|
||||||
headers[authHeader] = bearerToken;
|
headers[authHeader] = bearerToken;
|
||||||
|
|
|
@ -12,6 +12,7 @@ import {
|
||||||
getHeaders,
|
getHeaders,
|
||||||
LLMApi,
|
LLMApi,
|
||||||
LLMModel,
|
LLMModel,
|
||||||
|
SpeechOptions,
|
||||||
MultimodalContent,
|
MultimodalContent,
|
||||||
} from "../api";
|
} from "../api";
|
||||||
import Locale from "../../locales";
|
import Locale from "../../locales";
|
||||||
|
@ -83,6 +84,10 @@ export class QwenApi implements LLMApi {
|
||||||
return res?.output?.choices?.at(0)?.message?.content ?? "";
|
return res?.output?.choices?.at(0)?.message?.content ?? "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
speech(options: SpeechOptions): Promise<ArrayBuffer> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
|
||||||
async chat(options: ChatOptions) {
|
async chat(options: ChatOptions) {
|
||||||
const messages = options.messages.map((v) => ({
|
const messages = options.messages.map((v) => ({
|
||||||
role: v.role,
|
role: v.role,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { ACCESS_CODE_PREFIX, Anthropic, ApiPath } from "@/app/constant";
|
import { Anthropic, ApiPath } from "@/app/constant";
|
||||||
import { ChatOptions, getHeaders, LLMApi, MultimodalContent } from "../api";
|
import { ChatOptions, getHeaders, LLMApi, SpeechOptions } from "../api";
|
||||||
import {
|
import {
|
||||||
useAccessStore,
|
useAccessStore,
|
||||||
useAppConfig,
|
useAppConfig,
|
||||||
|
@ -9,13 +9,6 @@ import {
|
||||||
} from "@/app/store";
|
} from "@/app/store";
|
||||||
import { getClientConfig } from "@/app/config/client";
|
import { getClientConfig } from "@/app/config/client";
|
||||||
import { DEFAULT_API_HOST } from "@/app/constant";
|
import { DEFAULT_API_HOST } from "@/app/constant";
|
||||||
import {
|
|
||||||
EventStreamContentType,
|
|
||||||
fetchEventSource,
|
|
||||||
} from "@fortaine/fetch-event-source";
|
|
||||||
|
|
||||||
import Locale from "../../locales";
|
|
||||||
import { prettyObject } from "@/app/utils/format";
|
|
||||||
import { getMessageTextContent, isVisionModel } from "@/app/utils";
|
import { getMessageTextContent, isVisionModel } from "@/app/utils";
|
||||||
import { preProcessImageContent, stream } from "@/app/utils/chat";
|
import { preProcessImageContent, stream } from "@/app/utils/chat";
|
||||||
import { cloudflareAIGatewayUrl } from "@/app/utils/cloudflare";
|
import { cloudflareAIGatewayUrl } from "@/app/utils/cloudflare";
|
||||||
|
@ -80,6 +73,10 @@ const ClaudeMapper = {
|
||||||
const keys = ["claude-2, claude-instant-1"];
|
const keys = ["claude-2, claude-instant-1"];
|
||||||
|
|
||||||
export class ClaudeApi implements LLMApi {
|
export class ClaudeApi implements LLMApi {
|
||||||
|
speech(options: SpeechOptions): Promise<ArrayBuffer> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
|
||||||
extractMessage(res: any) {
|
extractMessage(res: any) {
|
||||||
console.log("[Response] claude response: ", res);
|
console.log("[Response] claude response: ", res);
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ import {
|
||||||
LLMApi,
|
LLMApi,
|
||||||
LLMModel,
|
LLMModel,
|
||||||
MultimodalContent,
|
MultimodalContent,
|
||||||
|
SpeechOptions,
|
||||||
} from "../api";
|
} from "../api";
|
||||||
import Locale from "../../locales";
|
import Locale from "../../locales";
|
||||||
import {
|
import {
|
||||||
|
@ -75,6 +76,10 @@ export class ErnieApi implements LLMApi {
|
||||||
return [baseUrl, path].join("/");
|
return [baseUrl, path].join("/");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
speech(options: SpeechOptions): Promise<ArrayBuffer> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
|
||||||
async chat(options: ChatOptions) {
|
async chat(options: ChatOptions) {
|
||||||
const messages = options.messages.map((v) => ({
|
const messages = options.messages.map((v) => ({
|
||||||
// "error_code": 336006, "error_msg": "the role of message with even index in the messages must be user or function",
|
// "error_code": 336006, "error_msg": "the role of message with even index in the messages must be user or function",
|
||||||
|
|
|
@ -13,6 +13,7 @@ import {
|
||||||
LLMApi,
|
LLMApi,
|
||||||
LLMModel,
|
LLMModel,
|
||||||
MultimodalContent,
|
MultimodalContent,
|
||||||
|
SpeechOptions,
|
||||||
} from "../api";
|
} from "../api";
|
||||||
import Locale from "../../locales";
|
import Locale from "../../locales";
|
||||||
import {
|
import {
|
||||||
|
@ -77,6 +78,10 @@ export class DoubaoApi implements LLMApi {
|
||||||
return res.choices?.at(0)?.message?.content ?? "";
|
return res.choices?.at(0)?.message?.content ?? "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
speech(options: SpeechOptions): Promise<ArrayBuffer> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
|
||||||
async chat(options: ChatOptions) {
|
async chat(options: ChatOptions) {
|
||||||
const messages = options.messages.map((v) => ({
|
const messages = options.messages.map((v) => ({
|
||||||
role: v.role,
|
role: v.role,
|
||||||
|
|
|
@ -1,5 +1,12 @@
|
||||||
import { ApiPath, Google, REQUEST_TIMEOUT_MS } from "@/app/constant";
|
import { ApiPath, Google, REQUEST_TIMEOUT_MS } from "@/app/constant";
|
||||||
import { ChatOptions, getHeaders, LLMApi, LLMModel, LLMUsage } from "../api";
|
import {
|
||||||
|
ChatOptions,
|
||||||
|
getHeaders,
|
||||||
|
LLMApi,
|
||||||
|
LLMModel,
|
||||||
|
LLMUsage,
|
||||||
|
SpeechOptions,
|
||||||
|
} from "../api";
|
||||||
import { useAccessStore, useAppConfig, useChatStore } from "@/app/store";
|
import { useAccessStore, useAppConfig, useChatStore } from "@/app/store";
|
||||||
import { getClientConfig } from "@/app/config/client";
|
import { getClientConfig } from "@/app/config/client";
|
||||||
import { DEFAULT_API_HOST } from "@/app/constant";
|
import { DEFAULT_API_HOST } from "@/app/constant";
|
||||||
|
@ -41,10 +48,6 @@ export class GeminiProApi implements LLMApi {
|
||||||
let chatPath = [baseUrl, path].join("/");
|
let chatPath = [baseUrl, path].join("/");
|
||||||
|
|
||||||
chatPath += chatPath.includes("?") ? "&alt=sse" : "?alt=sse";
|
chatPath += chatPath.includes("?") ? "&alt=sse" : "?alt=sse";
|
||||||
// if chatPath.startsWith('http') then add key in query string
|
|
||||||
if (chatPath.startsWith("http") && accessStore.googleApiKey) {
|
|
||||||
chatPath += `&key=${accessStore.googleApiKey}`;
|
|
||||||
}
|
|
||||||
return chatPath;
|
return chatPath;
|
||||||
}
|
}
|
||||||
extractMessage(res: any) {
|
extractMessage(res: any) {
|
||||||
|
@ -56,6 +59,10 @@ export class GeminiProApi implements LLMApi {
|
||||||
""
|
""
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
speech(options: SpeechOptions): Promise<ArrayBuffer> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
|
||||||
async chat(options: ChatOptions): Promise<void> {
|
async chat(options: ChatOptions): Promise<void> {
|
||||||
const apiClient = this;
|
const apiClient = this;
|
||||||
let multimodal = false;
|
let multimodal = false;
|
||||||
|
|
|
@ -7,7 +7,13 @@ import {
|
||||||
} from "@/app/constant";
|
} from "@/app/constant";
|
||||||
import { useAccessStore, useAppConfig, useChatStore } from "@/app/store";
|
import { useAccessStore, useAppConfig, useChatStore } from "@/app/store";
|
||||||
|
|
||||||
import { ChatOptions, getHeaders, LLMApi, LLMModel } from "../api";
|
import {
|
||||||
|
ChatOptions,
|
||||||
|
getHeaders,
|
||||||
|
LLMApi,
|
||||||
|
LLMModel,
|
||||||
|
SpeechOptions,
|
||||||
|
} from "../api";
|
||||||
import Locale from "../../locales";
|
import Locale from "../../locales";
|
||||||
import {
|
import {
|
||||||
EventStreamContentType,
|
EventStreamContentType,
|
||||||
|
@ -17,7 +23,7 @@ import { prettyObject } from "@/app/utils/format";
|
||||||
import { getClientConfig } from "@/app/config/client";
|
import { getClientConfig } from "@/app/config/client";
|
||||||
import { getMessageTextContent } from "@/app/utils";
|
import { getMessageTextContent } from "@/app/utils";
|
||||||
|
|
||||||
import { OpenAIListModelResponse, RequestPayload } from "./openai";
|
import { RequestPayload } from "./openai";
|
||||||
|
|
||||||
export class SparkApi implements LLMApi {
|
export class SparkApi implements LLMApi {
|
||||||
private disableListModels = true;
|
private disableListModels = true;
|
||||||
|
@ -53,6 +59,10 @@ export class SparkApi implements LLMApi {
|
||||||
return res.choices?.at(0)?.message?.content ?? "";
|
return res.choices?.at(0)?.message?.content ?? "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
speech(options: SpeechOptions): Promise<ArrayBuffer> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
|
||||||
async chat(options: ChatOptions) {
|
async chat(options: ChatOptions) {
|
||||||
const messages: ChatOptions["messages"] = [];
|
const messages: ChatOptions["messages"] = [];
|
||||||
for (const v of options.messages) {
|
for (const v of options.messages) {
|
||||||
|
|
|
@ -3,10 +3,8 @@
|
||||||
import {
|
import {
|
||||||
ApiPath,
|
ApiPath,
|
||||||
DEFAULT_API_HOST,
|
DEFAULT_API_HOST,
|
||||||
DEFAULT_MODELS,
|
|
||||||
Moonshot,
|
Moonshot,
|
||||||
REQUEST_TIMEOUT_MS,
|
REQUEST_TIMEOUT_MS,
|
||||||
ServiceProvider,
|
|
||||||
} from "@/app/constant";
|
} from "@/app/constant";
|
||||||
import {
|
import {
|
||||||
useAccessStore,
|
useAccessStore,
|
||||||
|
@ -15,28 +13,17 @@ import {
|
||||||
ChatMessageTool,
|
ChatMessageTool,
|
||||||
usePluginStore,
|
usePluginStore,
|
||||||
} from "@/app/store";
|
} from "@/app/store";
|
||||||
import { collectModelsWithDefaultModel } from "@/app/utils/model";
|
import { stream } from "@/app/utils/chat";
|
||||||
import { preProcessImageContent, stream } from "@/app/utils/chat";
|
|
||||||
import { cloudflareAIGatewayUrl } from "@/app/utils/cloudflare";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ChatOptions,
|
ChatOptions,
|
||||||
getHeaders,
|
getHeaders,
|
||||||
LLMApi,
|
LLMApi,
|
||||||
LLMModel,
|
LLMModel,
|
||||||
LLMUsage,
|
SpeechOptions,
|
||||||
MultimodalContent,
|
|
||||||
} from "../api";
|
} from "../api";
|
||||||
import Locale from "../../locales";
|
|
||||||
import {
|
|
||||||
EventStreamContentType,
|
|
||||||
fetchEventSource,
|
|
||||||
} from "@fortaine/fetch-event-source";
|
|
||||||
import { prettyObject } from "@/app/utils/format";
|
|
||||||
import { getClientConfig } from "@/app/config/client";
|
import { getClientConfig } from "@/app/config/client";
|
||||||
import { getMessageTextContent } from "@/app/utils";
|
import { getMessageTextContent } from "@/app/utils";
|
||||||
|
import { RequestPayload } from "./openai";
|
||||||
import { OpenAIListModelResponse, RequestPayload } from "./openai";
|
|
||||||
|
|
||||||
export class MoonshotApi implements LLMApi {
|
export class MoonshotApi implements LLMApi {
|
||||||
private disableListModels = true;
|
private disableListModels = true;
|
||||||
|
@ -72,6 +59,10 @@ export class MoonshotApi implements LLMApi {
|
||||||
return res.choices?.at(0)?.message?.content ?? "";
|
return res.choices?.at(0)?.message?.content ?? "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
speech(options: SpeechOptions): Promise<ArrayBuffer> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
|
||||||
async chat(options: ChatOptions) {
|
async chat(options: ChatOptions) {
|
||||||
const messages: ChatOptions["messages"] = [];
|
const messages: ChatOptions["messages"] = [];
|
||||||
for (const v of options.messages) {
|
for (const v of options.messages) {
|
||||||
|
|
|
@ -33,17 +33,12 @@ import {
|
||||||
LLMModel,
|
LLMModel,
|
||||||
LLMUsage,
|
LLMUsage,
|
||||||
MultimodalContent,
|
MultimodalContent,
|
||||||
|
SpeechOptions,
|
||||||
} from "../api";
|
} from "../api";
|
||||||
import Locale from "../../locales";
|
import Locale from "../../locales";
|
||||||
import {
|
|
||||||
EventStreamContentType,
|
|
||||||
fetchEventSource,
|
|
||||||
} from "@fortaine/fetch-event-source";
|
|
||||||
import { prettyObject } from "@/app/utils/format";
|
|
||||||
import { getClientConfig } from "@/app/config/client";
|
import { getClientConfig } from "@/app/config/client";
|
||||||
import {
|
import {
|
||||||
getMessageTextContent,
|
getMessageTextContent,
|
||||||
getMessageImages,
|
|
||||||
isVisionModel,
|
isVisionModel,
|
||||||
isDalle3 as _isDalle3,
|
isDalle3 as _isDalle3,
|
||||||
} from "@/app/utils";
|
} from "@/app/utils";
|
||||||
|
@ -147,6 +142,44 @@ export class ChatGPTApi implements LLMApi {
|
||||||
return res.choices?.at(0)?.message?.content ?? res;
|
return res.choices?.at(0)?.message?.content ?? res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async speech(options: SpeechOptions): Promise<ArrayBuffer> {
|
||||||
|
const requestPayload = {
|
||||||
|
model: options.model,
|
||||||
|
input: options.input,
|
||||||
|
voice: options.voice,
|
||||||
|
response_format: options.response_format,
|
||||||
|
speed: options.speed,
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log("[Request] openai speech payload: ", requestPayload);
|
||||||
|
|
||||||
|
const controller = new AbortController();
|
||||||
|
options.onController?.(controller);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const speechPath = this.path(OpenaiPath.SpeechPath);
|
||||||
|
const speechPayload = {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify(requestPayload),
|
||||||
|
signal: controller.signal,
|
||||||
|
headers: getHeaders(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// make a fetch request
|
||||||
|
const requestTimeoutId = setTimeout(
|
||||||
|
() => controller.abort(),
|
||||||
|
REQUEST_TIMEOUT_MS,
|
||||||
|
);
|
||||||
|
|
||||||
|
const res = await fetch(speechPath, speechPayload);
|
||||||
|
clearTimeout(requestTimeoutId);
|
||||||
|
return await res.arrayBuffer();
|
||||||
|
} catch (e) {
|
||||||
|
console.log("[Request] failed to make a speech request", e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async chat(options: ChatOptions) {
|
async chat(options: ChatOptions) {
|
||||||
const modelConfig = {
|
const modelConfig = {
|
||||||
...useAppConfig.getState().modelConfig,
|
...useAppConfig.getState().modelConfig,
|
||||||
|
@ -244,6 +277,7 @@ export class ChatGPTApi implements LLMApi {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (shouldStream) {
|
if (shouldStream) {
|
||||||
|
let index = -1;
|
||||||
const [tools, funcs] = usePluginStore
|
const [tools, funcs] = usePluginStore
|
||||||
.getState()
|
.getState()
|
||||||
.getAsTools(
|
.getAsTools(
|
||||||
|
@ -269,10 +303,10 @@ export class ChatGPTApi implements LLMApi {
|
||||||
}>;
|
}>;
|
||||||
const tool_calls = choices[0]?.delta?.tool_calls;
|
const tool_calls = choices[0]?.delta?.tool_calls;
|
||||||
if (tool_calls?.length > 0) {
|
if (tool_calls?.length > 0) {
|
||||||
const index = tool_calls[0]?.index;
|
|
||||||
const id = tool_calls[0]?.id;
|
const id = tool_calls[0]?.id;
|
||||||
const args = tool_calls[0]?.function?.arguments;
|
const args = tool_calls[0]?.function?.arguments;
|
||||||
if (id) {
|
if (id) {
|
||||||
|
index += 1;
|
||||||
runTools.push({
|
runTools.push({
|
||||||
id,
|
id,
|
||||||
type: tool_calls[0]?.type,
|
type: tool_calls[0]?.type,
|
||||||
|
@ -294,6 +328,8 @@ export class ChatGPTApi implements LLMApi {
|
||||||
toolCallMessage: any,
|
toolCallMessage: any,
|
||||||
toolCallResult: any[],
|
toolCallResult: any[],
|
||||||
) => {
|
) => {
|
||||||
|
// reset index value
|
||||||
|
index = -1;
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
requestPayload?.messages?.splice(
|
requestPayload?.messages?.splice(
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
|
|
@ -8,6 +8,7 @@ import {
|
||||||
LLMApi,
|
LLMApi,
|
||||||
LLMModel,
|
LLMModel,
|
||||||
MultimodalContent,
|
MultimodalContent,
|
||||||
|
SpeechOptions,
|
||||||
} from "../api";
|
} from "../api";
|
||||||
import Locale from "../../locales";
|
import Locale from "../../locales";
|
||||||
import {
|
import {
|
||||||
|
@ -89,6 +90,10 @@ export class HunyuanApi implements LLMApi {
|
||||||
return res.Choices?.at(0)?.Message?.Content ?? "";
|
return res.Choices?.at(0)?.Message?.Content ?? "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
speech(options: SpeechOptions): Promise<ArrayBuffer> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
|
||||||
async chat(options: ChatOptions) {
|
async chat(options: ChatOptions) {
|
||||||
const visionModel = isVisionModel(options.config.model);
|
const visionModel = isVisionModel(options.config.model);
|
||||||
const messages = options.messages.map((v, index) => ({
|
const messages = options.messages.map((v, index) => ({
|
||||||
|
|
|
@ -38,6 +38,7 @@ interface ChatCommands {
|
||||||
next?: Command;
|
next?: Command;
|
||||||
prev?: Command;
|
prev?: Command;
|
||||||
clear?: Command;
|
clear?: Command;
|
||||||
|
fork?: Command;
|
||||||
del?: Command;
|
del?: Command;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,6 @@ import {
|
||||||
useImperativeHandle,
|
useImperativeHandle,
|
||||||
} from "react";
|
} from "react";
|
||||||
import { useParams } from "react-router";
|
import { useParams } from "react-router";
|
||||||
import { useWindowSize } from "@/app/utils";
|
|
||||||
import { IconButton } from "./button";
|
import { IconButton } from "./button";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
import ExportIcon from "../icons/share.svg";
|
import ExportIcon from "../icons/share.svg";
|
||||||
|
|
|
@ -1,12 +1,70 @@
|
||||||
.auth-page {
|
.auth-page {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: flex-start;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
.top-banner {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding: 12px 64px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background: var(--second);
|
||||||
|
.top-banner-inner {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 150%;
|
||||||
|
span {
|
||||||
|
gap: 8px;
|
||||||
|
a {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
text-decoration: none;
|
||||||
|
margin-left: 8px;
|
||||||
|
color: var(--primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.top-banner-close {
|
||||||
|
cursor: pointer;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
right: 48px;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.top-banner {
|
||||||
|
padding: 12px 24px 12px 12px;
|
||||||
|
.top-banner-close {
|
||||||
|
right: 10px;
|
||||||
|
}
|
||||||
|
.top-banner-inner {
|
||||||
|
.top-banner-logo {
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
animation: slide-in-from-top ease 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
.auth-logo {
|
.auth-logo {
|
||||||
|
margin-top: 10vh;
|
||||||
transform: scale(1.4);
|
transform: scale(1.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,6 +72,7 @@
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
line-height: 2;
|
line-height: 2;
|
||||||
|
margin-bottom: 1vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
.auth-tips {
|
.auth-tips {
|
||||||
|
@ -24,6 +83,10 @@
|
||||||
margin: 3vh 0;
|
margin: 3vh 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.auth-input-second {
|
||||||
|
margin: 0 0 3vh 0;
|
||||||
|
}
|
||||||
|
|
||||||
.auth-actions {
|
.auth-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
|
@ -1,21 +1,34 @@
|
||||||
import styles from "./auth.module.scss";
|
import styles from "./auth.module.scss";
|
||||||
import { IconButton } from "./button";
|
import { IconButton } from "./button";
|
||||||
|
import { useState, useEffect } from "react";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { Path } from "../constant";
|
import { Path, SAAS_CHAT_URL } from "../constant";
|
||||||
import { useAccessStore } from "../store";
|
import { useAccessStore } from "../store";
|
||||||
import Locale from "../locales";
|
import Locale from "../locales";
|
||||||
|
import Delete from "../icons/close.svg";
|
||||||
|
import Arrow from "../icons/arrow.svg";
|
||||||
|
import Logo from "../icons/logo.svg";
|
||||||
|
import { useMobileScreen } from "@/app/utils";
|
||||||
import BotIcon from "../icons/bot.svg";
|
import BotIcon from "../icons/bot.svg";
|
||||||
import { useEffect } from "react";
|
|
||||||
import { getClientConfig } from "../config/client";
|
import { getClientConfig } from "../config/client";
|
||||||
|
import LeftIcon from "@/app/icons/left.svg";
|
||||||
|
import { safeLocalStorage } from "@/app/utils";
|
||||||
|
import {
|
||||||
|
trackSettingsPageGuideToCPaymentClick,
|
||||||
|
trackAuthorizationPageButtonToCPaymentClick,
|
||||||
|
} from "../utils/auth-settings-events";
|
||||||
|
const storage = safeLocalStorage();
|
||||||
|
|
||||||
export function AuthPage() {
|
export function AuthPage() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const accessStore = useAccessStore();
|
const accessStore = useAccessStore();
|
||||||
|
|
||||||
const goHome = () => navigate(Path.Home);
|
const goHome = () => navigate(Path.Home);
|
||||||
const goChat = () => navigate(Path.Chat);
|
const goChat = () => navigate(Path.Chat);
|
||||||
|
const goSaas = () => {
|
||||||
|
trackAuthorizationPageButtonToCPaymentClick();
|
||||||
|
window.location.href = SAAS_CHAT_URL;
|
||||||
|
};
|
||||||
|
|
||||||
const resetAccessCode = () => {
|
const resetAccessCode = () => {
|
||||||
accessStore.update((access) => {
|
accessStore.update((access) => {
|
||||||
access.openaiApiKey = "";
|
access.openaiApiKey = "";
|
||||||
|
@ -32,6 +45,14 @@ export function AuthPage() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles["auth-page"]}>
|
<div className={styles["auth-page"]}>
|
||||||
|
<TopBanner></TopBanner>
|
||||||
|
<div className={styles["auth-header"]}>
|
||||||
|
<IconButton
|
||||||
|
icon={<LeftIcon />}
|
||||||
|
text={Locale.Auth.Return}
|
||||||
|
onClick={() => navigate(Path.Home)}
|
||||||
|
></IconButton>
|
||||||
|
</div>
|
||||||
<div className={`no-dark ${styles["auth-logo"]}`}>
|
<div className={`no-dark ${styles["auth-logo"]}`}>
|
||||||
<BotIcon />
|
<BotIcon />
|
||||||
</div>
|
</div>
|
||||||
|
@ -65,7 +86,7 @@ export function AuthPage() {
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
className={styles["auth-input"]}
|
className={styles["auth-input-second"]}
|
||||||
type="password"
|
type="password"
|
||||||
placeholder={Locale.Settings.Access.Google.ApiKey.Placeholder}
|
placeholder={Locale.Settings.Access.Google.ApiKey.Placeholder}
|
||||||
value={accessStore.googleApiKey}
|
value={accessStore.googleApiKey}
|
||||||
|
@ -85,13 +106,74 @@ export function AuthPage() {
|
||||||
onClick={goChat}
|
onClick={goChat}
|
||||||
/>
|
/>
|
||||||
<IconButton
|
<IconButton
|
||||||
text={Locale.Auth.Later}
|
text={Locale.Auth.SaasTips}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
resetAccessCode();
|
goSaas();
|
||||||
goHome();
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function TopBanner() {
|
||||||
|
const [isHovered, setIsHovered] = useState(false);
|
||||||
|
const [isVisible, setIsVisible] = useState(true);
|
||||||
|
const isMobile = useMobileScreen();
|
||||||
|
useEffect(() => {
|
||||||
|
// 检查 localStorage 中是否有标记
|
||||||
|
const bannerDismissed = storage.getItem("bannerDismissed");
|
||||||
|
// 如果标记不存在,存储默认值并显示横幅
|
||||||
|
if (!bannerDismissed) {
|
||||||
|
storage.setItem("bannerDismissed", "false");
|
||||||
|
setIsVisible(true); // 显示横幅
|
||||||
|
} else if (bannerDismissed === "true") {
|
||||||
|
// 如果标记为 "true",则隐藏横幅
|
||||||
|
setIsVisible(false);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleMouseEnter = () => {
|
||||||
|
setIsHovered(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMouseLeave = () => {
|
||||||
|
setIsHovered(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
setIsVisible(false);
|
||||||
|
storage.setItem("bannerDismissed", "true");
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!isVisible) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={styles["top-banner"]}
|
||||||
|
onMouseEnter={handleMouseEnter}
|
||||||
|
onMouseLeave={handleMouseLeave}
|
||||||
|
>
|
||||||
|
<div className={`${styles["top-banner-inner"]} no-dark`}>
|
||||||
|
<Logo className={styles["top-banner-logo"]}></Logo>
|
||||||
|
<span>
|
||||||
|
{Locale.Auth.TopTips}
|
||||||
|
<a
|
||||||
|
href={SAAS_CHAT_URL}
|
||||||
|
rel="stylesheet"
|
||||||
|
onClick={() => {
|
||||||
|
trackSettingsPageGuideToCPaymentClick();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Locale.Settings.Access.SaasStart.ChatNow}
|
||||||
|
<Arrow style={{ marginLeft: "4px" }} />
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{(isHovered || isMobile) && (
|
||||||
|
<Delete className={styles["top-banner-close"]} onClick={handleClose} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import DeleteIcon from "../icons/delete.svg";
|
import DeleteIcon from "../icons/delete.svg";
|
||||||
import BotIcon from "../icons/bot.svg";
|
|
||||||
|
|
||||||
import styles from "./home.module.scss";
|
import styles from "./home.module.scss";
|
||||||
import {
|
import {
|
||||||
|
@ -12,7 +11,7 @@ import {
|
||||||
import { useChatStore } from "../store";
|
import { useChatStore } from "../store";
|
||||||
|
|
||||||
import Locale from "../locales";
|
import Locale from "../locales";
|
||||||
import { Link, useLocation, useNavigate } from "react-router-dom";
|
import { useLocation, useNavigate } from "react-router-dom";
|
||||||
import { Path } from "../constant";
|
import { Path } from "../constant";
|
||||||
import { MaskAvatar } from "./mask";
|
import { MaskAvatar } from "./mask";
|
||||||
import { Mask } from "../store/mask";
|
import { Mask } from "../store/mask";
|
||||||
|
|
|
@ -15,6 +15,8 @@ import RenameIcon from "../icons/rename.svg";
|
||||||
import ExportIcon from "../icons/share.svg";
|
import ExportIcon from "../icons/share.svg";
|
||||||
import ReturnIcon from "../icons/return.svg";
|
import ReturnIcon from "../icons/return.svg";
|
||||||
import CopyIcon from "../icons/copy.svg";
|
import CopyIcon from "../icons/copy.svg";
|
||||||
|
import SpeakIcon from "../icons/speak.svg";
|
||||||
|
import SpeakStopIcon from "../icons/speak-stop.svg";
|
||||||
import LoadingIcon from "../icons/three-dots.svg";
|
import LoadingIcon from "../icons/three-dots.svg";
|
||||||
import LoadingButtonIcon from "../icons/loading.svg";
|
import LoadingButtonIcon from "../icons/loading.svg";
|
||||||
import PromptIcon from "../icons/prompt.svg";
|
import PromptIcon from "../icons/prompt.svg";
|
||||||
|
@ -96,7 +98,8 @@ import {
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import {
|
import {
|
||||||
CHAT_PAGE_SIZE,
|
CHAT_PAGE_SIZE,
|
||||||
LAST_INPUT_KEY,
|
DEFAULT_TTS_ENGINE,
|
||||||
|
ModelProvider,
|
||||||
Path,
|
Path,
|
||||||
REQUEST_TIMEOUT_MS,
|
REQUEST_TIMEOUT_MS,
|
||||||
UNFINISHED_INPUT,
|
UNFINISHED_INPUT,
|
||||||
|
@ -113,6 +116,11 @@ import { useAllModels } from "../utils/hooks";
|
||||||
import { MultimodalContent } from "../client/api";
|
import { MultimodalContent } from "../client/api";
|
||||||
|
|
||||||
const localStorage = safeLocalStorage();
|
const localStorage = safeLocalStorage();
|
||||||
|
import { ClientApi } from "../client/api";
|
||||||
|
import { createTTSPlayer } from "../utils/audio";
|
||||||
|
import { MsEdgeTTS, OUTPUT_FORMAT } from "../utils/ms_edge_tts";
|
||||||
|
|
||||||
|
const ttsPlayer = createTTSPlayer();
|
||||||
|
|
||||||
const Markdown = dynamic(async () => (await import("./markdown")).Markdown, {
|
const Markdown = dynamic(async () => (await import("./markdown")).Markdown, {
|
||||||
loading: () => <LoadingIcon />,
|
loading: () => <LoadingIcon />,
|
||||||
|
@ -443,6 +451,7 @@ export function ChatActions(props: {
|
||||||
hitBottom: boolean;
|
hitBottom: boolean;
|
||||||
uploading: boolean;
|
uploading: boolean;
|
||||||
setShowShortcutKeyModal: React.Dispatch<React.SetStateAction<boolean>>;
|
setShowShortcutKeyModal: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
|
setUserInput: (input: string) => void;
|
||||||
}) {
|
}) {
|
||||||
const config = useAppConfig();
|
const config = useAppConfig();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
@ -981,6 +990,7 @@ function _Chat() {
|
||||||
chatStore.updateCurrentSession(
|
chatStore.updateCurrentSession(
|
||||||
(session) => (session.clearContextIndex = session.messages.length),
|
(session) => (session.clearContextIndex = session.messages.length),
|
||||||
),
|
),
|
||||||
|
fork: () => chatStore.forkSession(),
|
||||||
del: () => chatStore.deleteSession(chatStore.currentSessionIndex),
|
del: () => chatStore.deleteSession(chatStore.currentSessionIndex),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1184,10 +1194,55 @@ function _Chat() {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const accessStore = useAccessStore();
|
||||||
|
const [speechStatus, setSpeechStatus] = useState(false);
|
||||||
|
const [speechLoading, setSpeechLoading] = useState(false);
|
||||||
|
async function openaiSpeech(text: string) {
|
||||||
|
if (speechStatus) {
|
||||||
|
ttsPlayer.stop();
|
||||||
|
setSpeechStatus(false);
|
||||||
|
} else {
|
||||||
|
var api: ClientApi;
|
||||||
|
api = new ClientApi(ModelProvider.GPT);
|
||||||
|
const config = useAppConfig.getState();
|
||||||
|
setSpeechLoading(true);
|
||||||
|
ttsPlayer.init();
|
||||||
|
let audioBuffer: ArrayBuffer;
|
||||||
|
const { markdownToTxt } = require("markdown-to-txt");
|
||||||
|
const textContent = markdownToTxt(text);
|
||||||
|
if (config.ttsConfig.engine !== DEFAULT_TTS_ENGINE) {
|
||||||
|
const edgeVoiceName = accessStore.edgeVoiceName();
|
||||||
|
const tts = new MsEdgeTTS();
|
||||||
|
await tts.setMetadata(
|
||||||
|
edgeVoiceName,
|
||||||
|
OUTPUT_FORMAT.AUDIO_24KHZ_96KBITRATE_MONO_MP3,
|
||||||
|
);
|
||||||
|
audioBuffer = await tts.toArrayBuffer(textContent);
|
||||||
|
} else {
|
||||||
|
audioBuffer = await api.llm.speech({
|
||||||
|
model: config.ttsConfig.model,
|
||||||
|
input: textContent,
|
||||||
|
voice: config.ttsConfig.voice,
|
||||||
|
speed: config.ttsConfig.speed,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setSpeechStatus(true);
|
||||||
|
ttsPlayer
|
||||||
|
.play(audioBuffer, () => {
|
||||||
|
setSpeechStatus(false);
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.error("[OpenAI Speech]", e);
|
||||||
|
showToast(prettyObject(e));
|
||||||
|
setSpeechStatus(false);
|
||||||
|
})
|
||||||
|
.finally(() => setSpeechLoading(false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const context: RenderMessage[] = useMemo(() => {
|
const context: RenderMessage[] = useMemo(() => {
|
||||||
return session.mask.hideContext ? [] : session.mask.context.slice();
|
return session.mask.hideContext ? [] : session.mask.context.slice();
|
||||||
}, [session.mask.context, session.mask.hideContext]);
|
}, [session.mask.context, session.mask.hideContext]);
|
||||||
const accessStore = useAccessStore();
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
context.length === 0 &&
|
context.length === 0 &&
|
||||||
|
@ -1724,6 +1779,25 @@ function _Chat() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
{config.ttsConfig.enable && (
|
||||||
|
<ChatAction
|
||||||
|
text={
|
||||||
|
speechStatus
|
||||||
|
? Locale.Chat.Actions.StopSpeech
|
||||||
|
: Locale.Chat.Actions.Speech
|
||||||
|
}
|
||||||
|
icon={
|
||||||
|
speechStatus ? (
|
||||||
|
<SpeakStopIcon />
|
||||||
|
) : (
|
||||||
|
<SpeakIcon />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
onClick={() =>
|
||||||
|
openaiSpeech(getMessageTextContent(message))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -1842,6 +1916,7 @@ function _Chat() {
|
||||||
onSearch("");
|
onSearch("");
|
||||||
}}
|
}}
|
||||||
setShowShortcutKeyModal={setShowShortcutKeyModal}
|
setShowShortcutKeyModal={setShowShortcutKeyModal}
|
||||||
|
setUserInput={setUserInput}
|
||||||
/>
|
/>
|
||||||
<label
|
<label
|
||||||
className={`${styles["chat-input-panel-inner"]} ${
|
className={`${styles["chat-input-panel-inner"]} ${
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/* eslint-disable @next/next/no-img-element */
|
/* eslint-disable @next/next/no-img-element */
|
||||||
import { ChatMessage, ModelType, useAppConfig, useChatStore } from "../store";
|
import { ChatMessage, useAppConfig, useChatStore } from "../store";
|
||||||
import Locale from "../locales";
|
import Locale from "../locales";
|
||||||
import styles from "./exporter.module.scss";
|
import styles from "./exporter.module.scss";
|
||||||
import {
|
import {
|
||||||
|
|
|
@ -22,6 +22,8 @@ import {
|
||||||
import { useChatStore } from "../store";
|
import { useChatStore } from "../store";
|
||||||
import { IconButton } from "./button";
|
import { IconButton } from "./button";
|
||||||
|
|
||||||
|
import { useAppConfig } from "../store/config";
|
||||||
|
|
||||||
export function Mermaid(props: { code: string }) {
|
export function Mermaid(props: { code: string }) {
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
const [hasError, setHasError] = useState(false);
|
const [hasError, setHasError] = useState(false);
|
||||||
|
@ -92,7 +94,9 @@ export function PreCode(props: { children: any }) {
|
||||||
}
|
}
|
||||||
}, 600);
|
}, 600);
|
||||||
|
|
||||||
const enableArtifacts = session.mask?.enableArtifacts !== false;
|
const config = useAppConfig();
|
||||||
|
const enableArtifacts =
|
||||||
|
session.mask?.enableArtifacts !== false && config.enableArtifacts;
|
||||||
|
|
||||||
//Wrap the paragraph for plain-text
|
//Wrap the paragraph for plain-text
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -128,8 +132,9 @@ export function PreCode(props: { children: any }) {
|
||||||
className="copy-code-button"
|
className="copy-code-button"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (ref.current) {
|
if (ref.current) {
|
||||||
const code = ref.current.innerText;
|
copyToClipboard(
|
||||||
copyToClipboard(code);
|
ref.current.querySelector("code")?.innerText ?? "",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
></span>
|
></span>
|
||||||
|
@ -247,7 +252,7 @@ function tryWrapHtmlCode(text: string) {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.replace(
|
.replace(
|
||||||
/(<\/body>)([\r\n\s]*?)(<\/html>)([\n\r]*?)([`]*?)([\n\r]*?)/g,
|
/(<\/body>)([\r\n\s]*?)(<\/html>)([\n\r]*)([`]*)([\n\r]*?)/g,
|
||||||
(match, bodyEnd, space, htmlEnd, newLine, quoteEnd) => {
|
(match, bodyEnd, space, htmlEnd, newLine, quoteEnd) => {
|
||||||
return !quoteEnd ? bodyEnd + space + htmlEnd + "\n```\n" : match;
|
return !quoteEnd ? bodyEnd + space + htmlEnd + "\n```\n" : match;
|
||||||
},
|
},
|
||||||
|
@ -278,6 +283,20 @@ function _MarkDownContent(props: { content: string }) {
|
||||||
p: (pProps) => <p {...pProps} dir="auto" />,
|
p: (pProps) => <p {...pProps} dir="auto" />,
|
||||||
a: (aProps) => {
|
a: (aProps) => {
|
||||||
const href = aProps.href || "";
|
const href = aProps.href || "";
|
||||||
|
if (/\.(aac|mp3|opus|wav)$/.test(href)) {
|
||||||
|
return (
|
||||||
|
<figure>
|
||||||
|
<audio controls src={href}></audio>
|
||||||
|
</figure>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (/\.(3gp|3g2|webm|ogv|mpeg|mp4|avi)$/.test(href)) {
|
||||||
|
return (
|
||||||
|
<video controls width="99.9%">
|
||||||
|
<source src={href} />
|
||||||
|
</video>
|
||||||
|
);
|
||||||
|
}
|
||||||
const isInternal = /^\/#/i.test(href);
|
const isInternal = /^\/#/i.test(href);
|
||||||
const target = isInternal ? "_self" : aProps.target ?? "_blank";
|
const target = isInternal ? "_self" : aProps.target ?? "_blank";
|
||||||
return <a {...aProps} target={target} />;
|
return <a {...aProps} target={target} />;
|
||||||
|
|
|
@ -37,7 +37,7 @@ import Locale, { AllLangs, ALL_LANG_OPTIONS, 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 {
|
import {
|
||||||
copyToClipboard,
|
copyToClipboard,
|
||||||
downloadAs,
|
downloadAs,
|
||||||
|
@ -48,7 +48,6 @@ import { Updater } from "../typing";
|
||||||
import { ModelConfigList } from "./model-config";
|
import { ModelConfigList } from "./model-config";
|
||||||
import { FileName, Path } from "../constant";
|
import { FileName, Path } from "../constant";
|
||||||
import { BUILTIN_MASK_STORE } from "../masks";
|
import { BUILTIN_MASK_STORE } from "../masks";
|
||||||
import { nanoid } from "nanoid";
|
|
||||||
import {
|
import {
|
||||||
DragDropContext,
|
DragDropContext,
|
||||||
Droppable,
|
Droppable,
|
||||||
|
@ -167,21 +166,23 @@ export function MaskConfig(props: {
|
||||||
></input>
|
></input>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
<ListItem
|
{globalConfig.enableArtifacts && (
|
||||||
title={Locale.Mask.Config.Artifacts.Title}
|
<ListItem
|
||||||
subTitle={Locale.Mask.Config.Artifacts.SubTitle}
|
title={Locale.Mask.Config.Artifacts.Title}
|
||||||
>
|
subTitle={Locale.Mask.Config.Artifacts.SubTitle}
|
||||||
<input
|
>
|
||||||
aria-label={Locale.Mask.Config.Artifacts.Title}
|
<input
|
||||||
type="checkbox"
|
aria-label={Locale.Mask.Config.Artifacts.Title}
|
||||||
checked={props.mask.enableArtifacts !== false}
|
type="checkbox"
|
||||||
onChange={(e) => {
|
checked={props.mask.enableArtifacts !== false}
|
||||||
props.updateMask((mask) => {
|
onChange={(e) => {
|
||||||
mask.enableArtifacts = e.currentTarget.checked;
|
props.updateMask((mask) => {
|
||||||
});
|
mask.enableArtifacts = e.currentTarget.checked;
|
||||||
}}
|
});
|
||||||
></input>
|
}}
|
||||||
</ListItem>
|
></input>
|
||||||
|
</ListItem>
|
||||||
|
)}
|
||||||
|
|
||||||
{!props.shouldSyncFromGlobal ? (
|
{!props.shouldSyncFromGlobal ? (
|
||||||
<ListItem
|
<ListItem
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
.select-compress-model {
|
||||||
|
width: 60%;
|
||||||
|
select {
|
||||||
|
max-width: 100%;
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,6 +6,7 @@ import { InputRange } from "./input-range";
|
||||||
import { ListItem, Select } from "./ui-lib";
|
import { ListItem, Select } from "./ui-lib";
|
||||||
import { useAllModels } from "../utils/hooks";
|
import { useAllModels } from "../utils/hooks";
|
||||||
import { groupBy } from "lodash-es";
|
import { groupBy } from "lodash-es";
|
||||||
|
import styles from "./model-config.module.scss";
|
||||||
|
|
||||||
export function ModelConfigList(props: {
|
export function ModelConfigList(props: {
|
||||||
modelConfig: ModelConfig;
|
modelConfig: ModelConfig;
|
||||||
|
@ -242,6 +243,7 @@ export function ModelConfigList(props: {
|
||||||
subTitle={Locale.Settings.CompressModel.SubTitle}
|
subTitle={Locale.Settings.CompressModel.SubTitle}
|
||||||
>
|
>
|
||||||
<Select
|
<Select
|
||||||
|
className={styles["select-compress-model"]}
|
||||||
aria-label={Locale.Settings.CompressModel.Title}
|
aria-label={Locale.Settings.CompressModel.Title}
|
||||||
value={compressModelValue}
|
value={compressModelValue}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
|
|
|
@ -10,7 +10,29 @@
|
||||||
max-height: 240px;
|
max-height: 240px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
min-width: 300px;
|
min-width: 280px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.plugin-schema {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
input {
|
||||||
|
margin-right: 20px;
|
||||||
|
|
||||||
|
@media screen and (max-width: 600px) {
|
||||||
|
margin-right: 0px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 600px) {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 5px;
|
||||||
|
|
||||||
|
button {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -12,7 +12,6 @@ import EditIcon from "../icons/edit.svg";
|
||||||
import AddIcon from "../icons/add.svg";
|
import AddIcon from "../icons/add.svg";
|
||||||
import CloseIcon from "../icons/close.svg";
|
import CloseIcon from "../icons/close.svg";
|
||||||
import DeleteIcon from "../icons/delete.svg";
|
import DeleteIcon from "../icons/delete.svg";
|
||||||
import EyeIcon from "../icons/eye.svg";
|
|
||||||
import ConfirmIcon from "../icons/confirm.svg";
|
import ConfirmIcon from "../icons/confirm.svg";
|
||||||
import ReloadIcon from "../icons/reload.svg";
|
import ReloadIcon from "../icons/reload.svg";
|
||||||
import GithubIcon from "../icons/github.svg";
|
import GithubIcon from "../icons/github.svg";
|
||||||
|
@ -28,8 +27,7 @@ import {
|
||||||
} from "./ui-lib";
|
} from "./ui-lib";
|
||||||
import Locale from "../locales";
|
import Locale from "../locales";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { useEffect, useState } from "react";
|
import { useState } from "react";
|
||||||
import { getClientConfig } from "../config/client";
|
|
||||||
|
|
||||||
export function PluginPage() {
|
export function PluginPage() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
@ -209,19 +207,11 @@ export function PluginPage() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles["mask-actions"]}>
|
<div className={styles["mask-actions"]}>
|
||||||
{m.builtin ? (
|
<IconButton
|
||||||
<IconButton
|
icon={<EditIcon />}
|
||||||
icon={<EyeIcon />}
|
text={Locale.Plugin.Item.Edit}
|
||||||
text={Locale.Plugin.Item.View}
|
onClick={() => setEditingPluginId(m.id)}
|
||||||
onClick={() => setEditingPluginId(m.id)}
|
/>
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<IconButton
|
|
||||||
icon={<EditIcon />}
|
|
||||||
text={Locale.Plugin.Item.Edit}
|
|
||||||
onClick={() => setEditingPluginId(m.id)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{!m.builtin && (
|
{!m.builtin && (
|
||||||
<IconButton
|
<IconButton
|
||||||
icon={<DeleteIcon />}
|
icon={<DeleteIcon />}
|
||||||
|
@ -325,30 +315,13 @@ export function PluginPage() {
|
||||||
></PasswordInput>
|
></PasswordInput>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
)}
|
)}
|
||||||
{!getClientConfig()?.isApp && (
|
|
||||||
<ListItem
|
|
||||||
title={Locale.Plugin.Auth.Proxy}
|
|
||||||
subTitle={Locale.Plugin.Auth.ProxyDescription}
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
checked={editingPlugin?.usingProxy}
|
|
||||||
style={{ minWidth: 16 }}
|
|
||||||
onChange={(e) => {
|
|
||||||
pluginStore.updatePlugin(editingPlugin.id, (plugin) => {
|
|
||||||
plugin.usingProxy = e.currentTarget.checked;
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
></input>
|
|
||||||
</ListItem>
|
|
||||||
)}
|
|
||||||
</List>
|
</List>
|
||||||
<List>
|
<List>
|
||||||
<ListItem title={Locale.Plugin.EditModal.Content}>
|
<ListItem title={Locale.Plugin.EditModal.Content}>
|
||||||
<div style={{ display: "flex", justifyContent: "flex-end" }}>
|
<div className={pluginStyles["plugin-schema"]}>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
style={{ minWidth: 200, marginRight: 20 }}
|
style={{ minWidth: 200 }}
|
||||||
onInput={(e) => setLoadUrl(e.currentTarget.value)}
|
onInput={(e) => setLoadUrl(e.currentTarget.value)}
|
||||||
></input>
|
></input>
|
||||||
<IconButton
|
<IconButton
|
||||||
|
|
|
@ -72,3 +72,9 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.subtitle-button {
|
||||||
|
button {
|
||||||
|
overflow:visible ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ 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 LoadingIcon from "../icons/three-dots.svg";
|
||||||
import EditIcon from "../icons/edit.svg";
|
import EditIcon from "../icons/edit.svg";
|
||||||
|
import FireIcon from "../icons/fire.svg";
|
||||||
import EyeIcon from "../icons/eye.svg";
|
import EyeIcon from "../icons/eye.svg";
|
||||||
import DownloadIcon from "../icons/download.svg";
|
import DownloadIcon from "../icons/download.svg";
|
||||||
import UploadIcon from "../icons/upload.svg";
|
import UploadIcon from "../icons/upload.svg";
|
||||||
|
@ -18,7 +19,7 @@ import ConfirmIcon from "../icons/confirm.svg";
|
||||||
import ConnectionIcon from "../icons/connection.svg";
|
import ConnectionIcon from "../icons/connection.svg";
|
||||||
import CloudSuccessIcon from "../icons/cloud-success.svg";
|
import CloudSuccessIcon from "../icons/cloud-success.svg";
|
||||||
import CloudFailIcon from "../icons/cloud-fail.svg";
|
import CloudFailIcon from "../icons/cloud-fail.svg";
|
||||||
|
import { trackSettingsPageGuideToCPaymentClick } from "../utils/auth-settings-events";
|
||||||
import {
|
import {
|
||||||
Input,
|
Input,
|
||||||
List,
|
List,
|
||||||
|
@ -69,6 +70,7 @@ import {
|
||||||
UPDATE_URL,
|
UPDATE_URL,
|
||||||
Stability,
|
Stability,
|
||||||
Iflytek,
|
Iflytek,
|
||||||
|
SAAS_CHAT_URL,
|
||||||
} from "../constant";
|
} from "../constant";
|
||||||
import { Prompt, SearchService, usePromptStore } from "../store/prompt";
|
import { Prompt, SearchService, usePromptStore } from "../store/prompt";
|
||||||
import { ErrorBoundary } from "./error";
|
import { ErrorBoundary } from "./error";
|
||||||
|
@ -80,6 +82,7 @@ import { useSyncStore } from "../store/sync";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
import { useMaskStore } from "../store/mask";
|
import { useMaskStore } from "../store/mask";
|
||||||
import { ProviderType } from "../utils/cloud";
|
import { ProviderType } from "../utils/cloud";
|
||||||
|
import { TTSConfigList } from "./tts-config";
|
||||||
|
|
||||||
function EditPromptModal(props: { id: string; onClose: () => void }) {
|
function EditPromptModal(props: { id: string; onClose: () => void }) {
|
||||||
const promptStore = usePromptStore();
|
const promptStore = usePromptStore();
|
||||||
|
@ -685,6 +688,31 @@ export function Settings() {
|
||||||
</ListItem>
|
</ListItem>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const saasStartComponent = (
|
||||||
|
<ListItem
|
||||||
|
className={styles["subtitle-button"]}
|
||||||
|
title={
|
||||||
|
Locale.Settings.Access.SaasStart.Title +
|
||||||
|
`${Locale.Settings.Access.SaasStart.Label}`
|
||||||
|
}
|
||||||
|
subTitle={Locale.Settings.Access.SaasStart.SubTitle}
|
||||||
|
>
|
||||||
|
<IconButton
|
||||||
|
aria={
|
||||||
|
Locale.Settings.Access.SaasStart.Title +
|
||||||
|
Locale.Settings.Access.SaasStart.ChatNow
|
||||||
|
}
|
||||||
|
icon={<FireIcon />}
|
||||||
|
type={"primary"}
|
||||||
|
text={Locale.Settings.Access.SaasStart.ChatNow}
|
||||||
|
onClick={() => {
|
||||||
|
trackSettingsPageGuideToCPaymentClick();
|
||||||
|
window.location.href = SAAS_CHAT_URL;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
);
|
||||||
|
|
||||||
const useCustomConfigComponent = // Conditionally render the following ListItem based on clientConfig.isApp
|
const useCustomConfigComponent = // Conditionally render the following ListItem based on clientConfig.isApp
|
||||||
!clientConfig?.isApp && ( // only show if isApp is false
|
!clientConfig?.isApp && ( // only show if isApp is false
|
||||||
<ListItem
|
<ListItem
|
||||||
|
@ -1464,6 +1492,23 @@ export function Settings() {
|
||||||
}
|
}
|
||||||
></input>
|
></input>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
|
<ListItem
|
||||||
|
title={Locale.Mask.Config.Artifacts.Title}
|
||||||
|
subTitle={Locale.Mask.Config.Artifacts.SubTitle}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
aria-label={Locale.Mask.Config.Artifacts.Title}
|
||||||
|
type="checkbox"
|
||||||
|
checked={config.enableArtifacts}
|
||||||
|
onChange={(e) =>
|
||||||
|
updateConfig(
|
||||||
|
(config) =>
|
||||||
|
(config.enableArtifacts = e.currentTarget.checked),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
></input>
|
||||||
|
</ListItem>
|
||||||
</List>
|
</List>
|
||||||
|
|
||||||
<SyncItems />
|
<SyncItems />
|
||||||
|
@ -1540,6 +1585,7 @@ export function Settings() {
|
||||||
</List>
|
</List>
|
||||||
|
|
||||||
<List id={SlotID.CustomModel}>
|
<List id={SlotID.CustomModel}>
|
||||||
|
{saasStartComponent}
|
||||||
{accessCodeComponent}
|
{accessCodeComponent}
|
||||||
|
|
||||||
{!accessStore.hideUserApiKey && (
|
{!accessStore.hideUserApiKey && (
|
||||||
|
@ -1646,6 +1692,17 @@ export function Settings() {
|
||||||
<UserPromptModal onClose={() => setShowPromptModal(false)} />
|
<UserPromptModal onClose={() => setShowPromptModal(false)} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<List>
|
||||||
|
<TTSConfigList
|
||||||
|
ttsConfig={config.ttsConfig}
|
||||||
|
updateConfig={(updater) => {
|
||||||
|
const ttsConfig = { ...config.ttsConfig };
|
||||||
|
updater(ttsConfig);
|
||||||
|
config.update((config) => (config.ttsConfig = ttsConfig));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</List>
|
||||||
|
|
||||||
<DangerItems />
|
<DangerItems />
|
||||||
</div>
|
</div>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
|
|
|
@ -7,7 +7,6 @@ import SettingsIcon from "../icons/settings.svg";
|
||||||
import GithubIcon from "../icons/github.svg";
|
import GithubIcon from "../icons/github.svg";
|
||||||
import ChatGptIcon from "../icons/chatgpt.svg";
|
import ChatGptIcon from "../icons/chatgpt.svg";
|
||||||
import AddIcon from "../icons/add.svg";
|
import AddIcon from "../icons/add.svg";
|
||||||
import CloseIcon from "../icons/close.svg";
|
|
||||||
import DeleteIcon from "../icons/delete.svg";
|
import DeleteIcon from "../icons/delete.svg";
|
||||||
import MaskIcon from "../icons/mask.svg";
|
import MaskIcon from "../icons/mask.svg";
|
||||||
import DragIcon from "../icons/drag.svg";
|
import DragIcon from "../icons/drag.svg";
|
||||||
|
@ -254,11 +253,6 @@ export function SideBar(props: { className?: string }) {
|
||||||
{showPluginSelector && (
|
{showPluginSelector && (
|
||||||
<Selector
|
<Selector
|
||||||
items={[
|
items={[
|
||||||
{
|
|
||||||
title: "👇 Please select the plugin you need to use",
|
|
||||||
value: "-",
|
|
||||||
disable: true,
|
|
||||||
},
|
|
||||||
...PLUGINS.map((item) => {
|
...PLUGINS.map((item) => {
|
||||||
return {
|
return {
|
||||||
title: item.name,
|
title: item.name,
|
||||||
|
|
|
@ -0,0 +1,133 @@
|
||||||
|
import { TTSConfig, TTSConfigValidator } from "../store";
|
||||||
|
|
||||||
|
import Locale from "../locales";
|
||||||
|
import { ListItem, Select } from "./ui-lib";
|
||||||
|
import {
|
||||||
|
DEFAULT_TTS_ENGINE,
|
||||||
|
DEFAULT_TTS_ENGINES,
|
||||||
|
DEFAULT_TTS_MODELS,
|
||||||
|
DEFAULT_TTS_VOICES,
|
||||||
|
} from "../constant";
|
||||||
|
import { InputRange } from "./input-range";
|
||||||
|
|
||||||
|
export function TTSConfigList(props: {
|
||||||
|
ttsConfig: TTSConfig;
|
||||||
|
updateConfig: (updater: (config: TTSConfig) => void) => void;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ListItem
|
||||||
|
title={Locale.Settings.TTS.Enable.Title}
|
||||||
|
subTitle={Locale.Settings.TTS.Enable.SubTitle}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={props.ttsConfig.enable}
|
||||||
|
onChange={(e) =>
|
||||||
|
props.updateConfig(
|
||||||
|
(config) => (config.enable = e.currentTarget.checked),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
></input>
|
||||||
|
</ListItem>
|
||||||
|
{/* <ListItem
|
||||||
|
title={Locale.Settings.TTS.Autoplay.Title}
|
||||||
|
subTitle={Locale.Settings.TTS.Autoplay.SubTitle}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={props.ttsConfig.autoplay}
|
||||||
|
onChange={(e) =>
|
||||||
|
props.updateConfig(
|
||||||
|
(config) => (config.autoplay = e.currentTarget.checked),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
></input>
|
||||||
|
</ListItem> */}
|
||||||
|
<ListItem title={Locale.Settings.TTS.Engine}>
|
||||||
|
<Select
|
||||||
|
value={props.ttsConfig.engine}
|
||||||
|
onChange={(e) => {
|
||||||
|
props.updateConfig(
|
||||||
|
(config) =>
|
||||||
|
(config.engine = TTSConfigValidator.engine(
|
||||||
|
e.currentTarget.value,
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{DEFAULT_TTS_ENGINES.map((v, i) => (
|
||||||
|
<option value={v} key={i}>
|
||||||
|
{v}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</ListItem>
|
||||||
|
{props.ttsConfig.engine === DEFAULT_TTS_ENGINE && (
|
||||||
|
<>
|
||||||
|
<ListItem title={Locale.Settings.TTS.Model}>
|
||||||
|
<Select
|
||||||
|
value={props.ttsConfig.model}
|
||||||
|
onChange={(e) => {
|
||||||
|
props.updateConfig(
|
||||||
|
(config) =>
|
||||||
|
(config.model = TTSConfigValidator.model(
|
||||||
|
e.currentTarget.value,
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{DEFAULT_TTS_MODELS.map((v, i) => (
|
||||||
|
<option value={v} key={i}>
|
||||||
|
{v}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</ListItem>
|
||||||
|
<ListItem
|
||||||
|
title={Locale.Settings.TTS.Voice.Title}
|
||||||
|
subTitle={Locale.Settings.TTS.Voice.SubTitle}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
value={props.ttsConfig.voice}
|
||||||
|
onChange={(e) => {
|
||||||
|
props.updateConfig(
|
||||||
|
(config) =>
|
||||||
|
(config.voice = TTSConfigValidator.voice(
|
||||||
|
e.currentTarget.value,
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{DEFAULT_TTS_VOICES.map((v, i) => (
|
||||||
|
<option value={v} key={i}>
|
||||||
|
{v}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</ListItem>
|
||||||
|
<ListItem
|
||||||
|
title={Locale.Settings.TTS.Speed.Title}
|
||||||
|
subTitle={Locale.Settings.TTS.Speed.SubTitle}
|
||||||
|
>
|
||||||
|
<InputRange
|
||||||
|
aria={Locale.Settings.TTS.Speed.Title}
|
||||||
|
value={props.ttsConfig.speed?.toFixed(1)}
|
||||||
|
min="0.3"
|
||||||
|
max="4.0"
|
||||||
|
step="0.1"
|
||||||
|
onChange={(e) => {
|
||||||
|
props.updateConfig(
|
||||||
|
(config) =>
|
||||||
|
(config.speed = TTSConfigValidator.speed(
|
||||||
|
e.currentTarget.valueAsNumber,
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
></InputRange>
|
||||||
|
</ListItem>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,119 @@
|
||||||
|
@import "../styles/animation.scss";
|
||||||
|
.plugin-page {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.plugin-page-body {
|
||||||
|
padding: 20px;
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
|
.plugin-filter {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
animation: slide-in ease 0.3s;
|
||||||
|
height: 40px;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
.search-bar {
|
||||||
|
flex-grow: 1;
|
||||||
|
max-width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-bar:focus {
|
||||||
|
border: 1px solid var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.plugin-filter-lang {
|
||||||
|
height: 100%;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plugin-create {
|
||||||
|
height: 100%;
|
||||||
|
margin-left: 10px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
min-width: 80px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.plugin-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 20px;
|
||||||
|
border: var(--border-in-light);
|
||||||
|
animation: slide-in ease 0.3s;
|
||||||
|
|
||||||
|
&:not(:last-child) {
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
border-top-left-radius: 10px;
|
||||||
|
border-top-right-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-bottom-left-radius: 10px;
|
||||||
|
border-bottom-right-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plugin-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.plugin-icon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plugin-title {
|
||||||
|
.plugin-name {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.plugin-info {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.plugin-runtime-warning {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #f86c6c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.plugin-actions {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
transition: all ease 0.3s;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 600px) {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
box-shadow: var(--card-shadow);
|
||||||
|
|
||||||
|
&:not(:last-child) {
|
||||||
|
border-bottom: var(--border-in-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.plugin-actions {
|
||||||
|
width: 100%;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding-top: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -62,14 +62,14 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.vertical{
|
&.vertical {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: start;
|
align-items: start;
|
||||||
.list-header{
|
.list-header {
|
||||||
.list-item-title{
|
.list-item-title {
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
}
|
}
|
||||||
.list-item-sub-title{
|
.list-item-sub-title {
|
||||||
margin-bottom: 2px;
|
margin-bottom: 2px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -310,7 +310,7 @@
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
z-index: 999;
|
z-index: 999;
|
||||||
|
|
||||||
.selector-item-disabled{
|
.selector-item-disabled {
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -336,3 +336,4 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -154,8 +154,8 @@ export const getServerSideConfig = () => {
|
||||||
// `[Server Config] using ${randomIndex + 1} of ${apiKeys.length} api key`,
|
// `[Server Config] using ${randomIndex + 1} of ${apiKeys.length} api key`,
|
||||||
// );
|
// );
|
||||||
|
|
||||||
const allowedWebDevEndpoints = (
|
const allowedWebDavEndpoints = (
|
||||||
process.env.WHITE_WEBDEV_ENDPOINTS ?? ""
|
process.env.WHITE_WEBDAV_ENDPOINTS ?? ""
|
||||||
).split(",");
|
).split(",");
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -229,6 +229,6 @@ export const getServerSideConfig = () => {
|
||||||
disableFastLink: !!process.env.DISABLE_FAST_LINK,
|
disableFastLink: !!process.env.DISABLE_FAST_LINK,
|
||||||
customModels,
|
customModels,
|
||||||
defaultModel,
|
defaultModel,
|
||||||
allowedWebDevEndpoints,
|
allowedWebDavEndpoints,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
import path from "path";
|
|
||||||
|
|
||||||
export const OWNER = "ChatGPTNextWeb";
|
export const OWNER = "ChatGPTNextWeb";
|
||||||
export const REPO = "ChatGPT-Next-Web";
|
export const REPO = "ChatGPT-Next-Web";
|
||||||
export const REPO_URL = `https://github.com/${OWNER}/${REPO}`;
|
export const REPO_URL = `https://github.com/${OWNER}/${REPO}`;
|
||||||
|
@ -152,6 +150,7 @@ export const Anthropic = {
|
||||||
|
|
||||||
export const OpenaiPath = {
|
export const OpenaiPath = {
|
||||||
ChatPath: "v1/chat/completions",
|
ChatPath: "v1/chat/completions",
|
||||||
|
SpeechPath: "v1/audio/speech",
|
||||||
ImagePath: "v1/images/generations",
|
ImagePath: "v1/images/generations",
|
||||||
UsagePath: "dashboard/billing/usage",
|
UsagePath: "dashboard/billing/usage",
|
||||||
SubsPath: "dashboard/billing/subscription",
|
SubsPath: "dashboard/billing/subscription",
|
||||||
|
@ -258,6 +257,20 @@ export const KnowledgeCutOffDate: Record<string, string> = {
|
||||||
"gemini-pro-vision": "2023-12",
|
"gemini-pro-vision": "2023-12",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const DEFAULT_TTS_ENGINE = "OpenAI-TTS";
|
||||||
|
export const DEFAULT_TTS_ENGINES = ["OpenAI-TTS", "Edge-TTS"];
|
||||||
|
export const DEFAULT_TTS_MODEL = "tts-1";
|
||||||
|
export const DEFAULT_TTS_VOICE = "alloy";
|
||||||
|
export const DEFAULT_TTS_MODELS = ["tts-1", "tts-1-hd"];
|
||||||
|
export const DEFAULT_TTS_VOICES = [
|
||||||
|
"alloy",
|
||||||
|
"echo",
|
||||||
|
"fable",
|
||||||
|
"onyx",
|
||||||
|
"nova",
|
||||||
|
"shimmer",
|
||||||
|
];
|
||||||
|
|
||||||
const openaiModels = [
|
const openaiModels = [
|
||||||
"gpt-3.5-turbo",
|
"gpt-3.5-turbo",
|
||||||
"gpt-3.5-turbo-1106",
|
"gpt-3.5-turbo-1106",
|
||||||
|
@ -279,7 +292,7 @@ const openaiModels = [
|
||||||
"gpt-4-1106-preview",
|
"gpt-4-1106-preview",
|
||||||
"dall-e-3",
|
"dall-e-3",
|
||||||
"o1-mini",
|
"o1-mini",
|
||||||
"o1-preview"
|
"o1-preview",
|
||||||
];
|
];
|
||||||
|
|
||||||
const googleModels = [
|
const googleModels = [
|
||||||
|
@ -488,3 +501,6 @@ export const PLUGINS = [
|
||||||
{ name: "Stable Diffusion", path: Path.Sd },
|
{ name: "Stable Diffusion", path: Path.Sd },
|
||||||
{ name: "Search Chat", path: Path.SearchChat },
|
{ name: "Search Chat", path: Path.SearchChat },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const SAAS_CHAT_URL = "https://nextchat.dev/chat";
|
||||||
|
export const SAAS_CHAT_UTM_URL = "https://nextchat.dev/chat?utm=github";
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
<svg class="icon--SJP_d" width="16" height="16" fill="none" viewBox="0 0 16 16" style="min-width: 16px; min-height: 16px;"><g><path data-follow-fill="currentColor" fill-rule="evenodd" clip-rule="evenodd" d="M5.248 14.444a.625.625 0 0 1-.005-.884l5.068-5.12a.625.625 0 0 0 0-.88L5.243 2.44a.625.625 0 1 1 .889-.88l5.067 5.121c.723.73.723 1.907 0 2.638l-5.067 5.12a.625.625 0 0 1-.884.005Z" fill="currentColor"></path></g></svg>
|
After Width: | Height: | Size: 426 B |
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M12.832 21.801c3.126-.626 7.168-2.875 7.168-8.69c0-5.291-3.873-8.815-6.658-10.434c-.619-.36-1.342.113-1.342.828v1.828c0 1.442-.606 4.074-2.29 5.169c-.86.559-1.79-.278-1.894-1.298l-.086-.838c-.1-.974-1.092-1.565-1.87-.971C4.461 8.46 3 10.33 3 13.11C3 20.221 8.289 22 10.933 22q.232 0 .484-.015C10.111 21.874 8 21.064 8 18.444c0-2.05 1.495-3.435 2.631-4.11c.306-.18.663.055.663.41v.59c0 .45.175 1.155.59 1.637c.47.546 1.159-.026 1.214-.744c.018-.226.246-.37.442-.256c.641.375 1.46 1.175 1.46 2.473c0 2.048-1.129 2.99-2.168 3.357"/></svg>
|
After Width: | Height: | Size: 648 B |
|
@ -0,0 +1,19 @@
|
||||||
|
<svg width="38.73" height="42" viewBox="0 0 221 240" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect x="160.697" y="38.125" width="65.007" height="145.932" rx="32.503" transform="rotate(21.987 160.697 38.125)" fill="url(#logo_svg__a)"></rect>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="m48.642 79.125-25.92 71.213c-6.139 16.869 2.558 35.52 19.427 41.66 16.868 6.14 35.52-2.558 41.66-19.426L94.23 143.94l-36.658-37.439a32.42 32.42 0 0 1-9.244-23.497c.033-1.326.14-2.62.314-3.879Z" fill="url(#logo_svg__b)"></path>
|
||||||
|
<path d="M172.578 132.787a32.765 32.765 0 0 1 8.981 24.238c-1.458 28.748-36.622 41.778-56.46 20.92l-67.644-71.122a32.763 32.763 0 0 1-8.981-24.238c1.457-28.748 36.622-41.778 56.46-20.92l67.644 71.122Z" fill="url(#logo_svg__c)" fill-opacity="0.96"></path>
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="logo_svg__a" x1="215.063" y1="59.628" x2="160.714" y2="157.96" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#3EADFE"></stop>
|
||||||
|
<stop offset="1" stop-color="#2A7AFF"></stop>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="logo_svg__b" x1="105.376" y1="84.416" x2="19.745" y2="131.163" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#01B3FF"></stop>
|
||||||
|
<stop offset="1" stop-color="#59ECFA"></stop>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="logo_svg__c" x1="102.734" y1="136.396" x2="192.577" y2="155.859" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#023BFF" stop-opacity="0.82"></stop>
|
||||||
|
<stop offset="0.88" stop-color="#2D86FF" stop-opacity="0.76"></stop>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" width="16" height="16" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4"><path stroke-linecap="round" stroke-linejoin="round" d="M17.25 9.75 19.5 12m0 0 2.25 2.25M19.5 12l2.25-2.25M19.5 12l-2.25 2.25m-10.5-6 4.72-4.72a.75.75 0 0 1 1.28.53v15.88a.75.75 0 0 1-1.28.53l-4.72-4.72H4.51c-.88 0-1.704-.507-1.938-1.354A9.009 9.009 0 0 1 2.25 12c0-.83.112-1.633.322-2.396C2.806 8.756 3.63 8.25 4.51 8.25H6.75Z"></path></svg>
|
After Width: | Height: | Size: 495 B |
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" width="16" height="16" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4"><path stroke-linecap="round" stroke-linejoin="round" d="M19.114 5.636a9 9 0 010 12.728M16.463 8.288a5.25 5.25 0 010 7.424M6.75 8.25l4.72-4.72a.75.75 0 011.28.53v15.88a.75.75 0 01-1.28.53l-4.72-4.72H4.51c-.88 0-1.704-.507-1.938-1.354A9.01 9.01 0 012.25 12c0-.83.112-1.633.322-2.396C2.806 8.756 3.63 8.25 4.51 8.25H6.75z"></path></svg>
|
After Width: | Height: | Size: 485 B |
|
@ -0,0 +1,16 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" fill="none" viewBox="0 0 20 20">
|
||||||
|
<defs>
|
||||||
|
<rect id="path_0" width="20" height="20" x="0" y="0" />
|
||||||
|
</defs>
|
||||||
|
<g opacity="1" transform="translate(0 0) rotate(0 8 8)">
|
||||||
|
<mask id="bg-mask-0" fill="#fff">
|
||||||
|
<use xlink:href="#path_0" />
|
||||||
|
</mask>
|
||||||
|
<g mask="url(#bg-mask-0)">
|
||||||
|
<path d="M7 4a3 3 0 016 0v6a3 3 0 11-6 0V4z" fill="#333333">
|
||||||
|
</path>
|
||||||
|
<path d="M5.5 9.643a.75.75 0 00-1.5 0V10c0 3.06 2.29 5.585 5.25 5.954V17.5h-1.5a.75.75 0 000 1.5h4.5a.75.75 0 000-1.5h-1.5v-1.546A6.001 6.001 0 0016 10v-.357a.75.75 0 00-1.5 0V10a4.5 4.5 0 01-9 0v-.357z" fill="#333333">
|
||||||
|
</path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 708 B |
|
@ -41,7 +41,11 @@ export default function RootLayout({
|
||||||
name="viewport"
|
name="viewport"
|
||||||
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"
|
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"
|
||||||
/>
|
/>
|
||||||
<link rel="manifest" href="/site.webmanifest" crossOrigin="use-credentials"></link>
|
<link
|
||||||
|
rel="manifest"
|
||||||
|
href="/site.webmanifest"
|
||||||
|
crossOrigin="use-credentials"
|
||||||
|
></link>
|
||||||
<script src="/serviceWorkerRegister.js" defer></script>
|
<script src="/serviceWorkerRegister.js" defer></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
|
@ -1,15 +1,21 @@
|
||||||
import { SubmitKey } from "../store/config";
|
import { SubmitKey } from "../store/config";
|
||||||
import type { PartialLocaleType } from "./index";
|
import type { PartialLocaleType } from "./index";
|
||||||
import { getClientConfig } from "../config/client";
|
import { getClientConfig } from "../config/client";
|
||||||
|
import { SAAS_CHAT_UTM_URL } from "@/app/constant";
|
||||||
const isApp = !!getClientConfig()?.isApp;
|
const isApp = !!getClientConfig()?.isApp;
|
||||||
|
|
||||||
const ar: PartialLocaleType = {
|
const ar: PartialLocaleType = {
|
||||||
WIP: "قريبًا...",
|
WIP: "قريبًا...",
|
||||||
Error: {
|
Error: {
|
||||||
Unauthorized: isApp
|
Unauthorized: isApp
|
||||||
? "تم اكتشاف مفتاح API غير صالح، يرجى الذهاب إلى [الإعدادات](/#/settings) للتحقق من صحة مفتاح API."
|
? `😆 واجهت المحادثة بعض المشكلات، لا داعي للقلق:
|
||||||
: "كلمة المرور غير صحيحة أو فارغة، يرجى الذهاب إلى [تسجيل الدخول](/#/auth) لإدخال كلمة مرور صحيحة، أو أدخل مفتاح OpenAI API الخاص بك في [الإعدادات](/#/settings).",
|
\\ 1️⃣ إذا كنت ترغب في تجربة دون إعداد، [انقر هنا لبدء المحادثة فورًا 🚀](${SAAS_CHAT_UTM_URL})
|
||||||
|
\\ 2️⃣ إذا كنت تريد استخدام موارد OpenAI الخاصة بك، انقر [هنا](/#/settings) لتعديل الإعدادات ⚙️`
|
||||||
|
: `😆 واجهت المحادثة بعض المشكلات، لا داعي للقلق:
|
||||||
|
\ 1️⃣ إذا كنت ترغب في تجربة دون إعداد، [انقر هنا لبدء المحادثة فورًا 🚀](${SAAS_CHAT_UTM_URL})
|
||||||
|
\ 2️⃣ إذا كنت تستخدم إصدار النشر الخاص، انقر [هنا](/#/auth) لإدخال مفتاح الوصول 🔑
|
||||||
|
\ 3️⃣ إذا كنت تريد استخدام موارد OpenAI الخاصة بك، انقر [هنا](/#/settings) لتعديل الإعدادات ⚙️
|
||||||
|
`,
|
||||||
},
|
},
|
||||||
Auth: {
|
Auth: {
|
||||||
Title: "تحتاج إلى كلمة مرور",
|
Title: "تحتاج إلى كلمة مرور",
|
||||||
|
@ -18,6 +24,10 @@ const ar: PartialLocaleType = {
|
||||||
Input: "أدخل رمز الوصول هنا",
|
Input: "أدخل رمز الوصول هنا",
|
||||||
Confirm: "تأكيد",
|
Confirm: "تأكيد",
|
||||||
Later: "في وقت لاحق",
|
Later: "في وقت لاحق",
|
||||||
|
Return: "عودة",
|
||||||
|
SaasTips: "الإعدادات معقدة، أريد استخدامه على الفور",
|
||||||
|
TopTips:
|
||||||
|
"🥳 عرض NextChat AI الأول، افتح الآن OpenAI o1, GPT-4o, Claude-3.5 وأحدث النماذج الكبيرة",
|
||||||
},
|
},
|
||||||
ChatItem: {
|
ChatItem: {
|
||||||
ChatItemCount: (count: number) => `${count} محادثة`,
|
ChatItemCount: (count: number) => `${count} محادثة`,
|
||||||
|
@ -281,6 +291,13 @@ const ar: PartialLocaleType = {
|
||||||
},
|
},
|
||||||
|
|
||||||
Access: {
|
Access: {
|
||||||
|
SaasStart: {
|
||||||
|
Title: "استخدام NextChat AI",
|
||||||
|
Label: "(أفضل حل من حيث التكلفة)",
|
||||||
|
SubTitle:
|
||||||
|
"مدعوم رسميًا من NextChat، جاهز للاستخدام بدون إعداد، يدعم أحدث النماذج الكبيرة مثل OpenAI o1 و GPT-4o و Claude-3.5",
|
||||||
|
ChatNow: "الدردشة الآن",
|
||||||
|
},
|
||||||
AccessCode: {
|
AccessCode: {
|
||||||
Title: "كلمة المرور للوصول",
|
Title: "كلمة المرور للوصول",
|
||||||
SubTitle: "قام المشرف بتمكين الوصول المشفر",
|
SubTitle: "قام المشرف بتمكين الوصول المشفر",
|
||||||
|
|
|
@ -1,15 +1,21 @@
|
||||||
import { SubmitKey } from "../store/config";
|
import { SubmitKey } from "../store/config";
|
||||||
import type { PartialLocaleType } from "./index";
|
import type { PartialLocaleType } from "./index";
|
||||||
import { getClientConfig } from "../config/client";
|
import { getClientConfig } from "../config/client";
|
||||||
|
import { SAAS_CHAT_UTM_URL } from "@/app/constant";
|
||||||
const isApp = !!getClientConfig()?.isApp;
|
const isApp = !!getClientConfig()?.isApp;
|
||||||
|
|
||||||
const bn: PartialLocaleType = {
|
const bn: PartialLocaleType = {
|
||||||
WIP: "শীঘ্রই আসছে...",
|
WIP: "শীঘ্রই আসছে...",
|
||||||
Error: {
|
Error: {
|
||||||
Unauthorized: isApp
|
Unauthorized: isApp
|
||||||
? "অবৈধ API কী সনাক্ত করা হয়েছে, অনুগ্রহ করে [সেটিংস](/#/settings) পৃষ্ঠায় যান এবং নিশ্চিত করুন যে API কী সঠিকভাবে কনফিগার করা হয়েছে।"
|
? `😆 কথোপকথনে কিছু সমস্যা হয়েছে, চিন্তার কিছু নেই:
|
||||||
: "অ্যাক্সেস পাসওয়ার্ড সঠিক নয় বা খালি, অনুগ্রহ করে [লগইন](/#/auth) পৃষ্ঠায় যান এবং সঠিক অ্যাক্সেস পাসওয়ার্ড প্রবেশ করান, অথবা [সেটিংস](/#/settings) পৃষ্ঠায় আপনার OpenAI API কী প্রবেশ করান।",
|
\\ 1️⃣ যদি আপনি শূন্য কনফিগারেশনে শুরু করতে চান, তাহলে [এখানে ক্লিক করে অবিলম্বে কথোপকথন শুরু করুন 🚀](${SAAS_CHAT_UTM_URL})
|
||||||
|
\\ 2️⃣ যদি আপনি আপনার নিজস্ব OpenAI সম্পদ ব্যবহার করতে চান, তাহলে [এখানে ক্লিক করুন](/#/settings) সেটিংস পরিবর্তন করতে ⚙️`
|
||||||
|
: `😆 কথোপকথনে কিছু সমস্যা হয়েছে, চিন্তার কিছু নেই:
|
||||||
|
\ 1️⃣ যদি আপনি শূন্য কনফিগারেশনে শুরু করতে চান, তাহলে [এখানে ক্লিক করে অবিলম্বে কথোপকথন শুরু করুন 🚀](${SAAS_CHAT_UTM_URL})
|
||||||
|
\ 2️⃣ যদি আপনি একটি প্রাইভেট ডেপ্লয়মেন্ট সংস্করণ ব্যবহার করেন, তাহলে [এখানে ক্লিক করুন](/#/auth) প্রবেশাধিকার কীগুলি প্রবেশ করতে 🔑
|
||||||
|
\ 3️⃣ যদি আপনি আপনার নিজস্ব OpenAI সম্পদ ব্যবহার করতে চান, তাহলে [এখানে ক্লিক করুন](/#/settings) সেটিংস পরিবর্তন করতে ⚙️
|
||||||
|
`,
|
||||||
},
|
},
|
||||||
Auth: {
|
Auth: {
|
||||||
Title: "পাসওয়ার্ড প্রয়োজন",
|
Title: "পাসওয়ার্ড প্রয়োজন",
|
||||||
|
@ -18,6 +24,10 @@ const bn: PartialLocaleType = {
|
||||||
Input: "এখানে অ্যাক্সেস কোড লিখুন",
|
Input: "এখানে অ্যাক্সেস কোড লিখুন",
|
||||||
Confirm: "নিশ্চিত করুন",
|
Confirm: "নিশ্চিত করুন",
|
||||||
Later: "পরে বলুন",
|
Later: "পরে বলুন",
|
||||||
|
Return: "ফিরে আসা",
|
||||||
|
SaasTips: "কনফিগারেশন খুব কঠিন, আমি অবিলম্বে ব্যবহার করতে চাই",
|
||||||
|
TopTips:
|
||||||
|
"🥳 NextChat AI প্রথম প্রকাশের অফার, এখনই OpenAI o1, GPT-4o, Claude-3.5 এবং সর্বশেষ বড় মডেলগুলি আনলক করুন",
|
||||||
},
|
},
|
||||||
ChatItem: {
|
ChatItem: {
|
||||||
ChatItemCount: (count: number) => `${count} টি চ্যাট`,
|
ChatItemCount: (count: number) => `${count} টি চ্যাট`,
|
||||||
|
@ -284,6 +294,14 @@ const bn: PartialLocaleType = {
|
||||||
},
|
},
|
||||||
|
|
||||||
Access: {
|
Access: {
|
||||||
|
SaasStart: {
|
||||||
|
Title: "NextChat AI ব্যবহার করুন",
|
||||||
|
Label: "(সেরা মূল্যসাশ্রয়ী সমাধান)",
|
||||||
|
SubTitle:
|
||||||
|
"NextChat কর্তৃক অফিসিয়াল রক্ষণাবেক্ষণ, শূন্য কনফিগারেশন ব্যবহার শুরু করুন, OpenAI o1, GPT-4o, Claude-3.5 সহ সর্বশেষ বড় মডেলগুলি সমর্থন করে",
|
||||||
|
ChatNow: "এখনই চ্যাট করুন",
|
||||||
|
},
|
||||||
|
|
||||||
AccessCode: {
|
AccessCode: {
|
||||||
Title: "অ্যাক্সেস পাসওয়ার্ড",
|
Title: "অ্যাক্সেস পাসওয়ার্ড",
|
||||||
SubTitle: "অ্যাডমিন এনক্রিপ্টেড অ্যাক্সেস সক্রিয় করেছেন",
|
SubTitle: "অ্যাডমিন এনক্রিপ্টেড অ্যাক্সেস সক্রিয় করেছেন",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { ShortcutKeyModal } from "../components/chat";
|
|
||||||
import { getClientConfig } from "../config/client";
|
import { getClientConfig } from "../config/client";
|
||||||
import { SubmitKey } from "../store/config";
|
import { SubmitKey } from "../store/config";
|
||||||
|
import { SAAS_CHAT_UTM_URL } from "@/app/constant";
|
||||||
|
|
||||||
const isApp = !!getClientConfig()?.isApp;
|
const isApp = !!getClientConfig()?.isApp;
|
||||||
|
|
||||||
|
@ -8,16 +8,26 @@ const cn = {
|
||||||
WIP: "该功能仍在开发中……",
|
WIP: "该功能仍在开发中……",
|
||||||
Error: {
|
Error: {
|
||||||
Unauthorized: isApp
|
Unauthorized: isApp
|
||||||
? "检测到无效 API Key,请前往[设置](/#/settings)页检查 API Key 是否配置正确。"
|
? `😆 对话遇到了一些问题,不用慌:
|
||||||
: "访问密码不正确或为空,请前往[登录](/#/auth)页输入正确的访问密码,或者在[设置](/#/settings)页填入你自己的 OpenAI API Key。",
|
\\ 1️⃣ 想要零配置开箱即用,[点击这里立刻开启对话 🚀](${SAAS_CHAT_UTM_URL})
|
||||||
|
\\ 2️⃣ 如果你想消耗自己的 OpenAI 资源,点击[这里](/#/settings)修改设置 ⚙️`
|
||||||
|
: `😆 对话遇到了一些问题,不用慌:
|
||||||
|
\ 1️⃣ 想要零配置开箱即用,[点击这里立刻开启对话 🚀](${SAAS_CHAT_UTM_URL})
|
||||||
|
\ 2️⃣ 如果你正在使用私有部署版本,点击[这里](/#/auth)输入访问秘钥 🔑
|
||||||
|
\ 3️⃣ 如果你想消耗自己的 OpenAI 资源,点击[这里](/#/settings)修改设置 ⚙️
|
||||||
|
`,
|
||||||
},
|
},
|
||||||
Auth: {
|
Auth: {
|
||||||
|
Return: "返回",
|
||||||
Title: "需要密码",
|
Title: "需要密码",
|
||||||
Tips: "管理员开启了密码验证,请在下方填入访问码",
|
Tips: "管理员开启了密码验证,请在下方填入访问码",
|
||||||
SubTips: "或者输入你的 OpenAI 或 Google API 密钥",
|
SubTips: "或者输入你的 OpenAI 或 Google AI 密钥",
|
||||||
Input: "在此处填写访问码",
|
Input: "在此处填写访问码",
|
||||||
Confirm: "确认",
|
Confirm: "确认",
|
||||||
Later: "稍后再说",
|
Later: "稍后再说",
|
||||||
|
SaasTips: "配置太麻烦,想要立即使用",
|
||||||
|
TopTips:
|
||||||
|
"🥳 NextChat AI 首发优惠,立刻解锁 OpenAI o1, GPT-4o, Claude-3.5 等最新大模型",
|
||||||
},
|
},
|
||||||
ChatItem: {
|
ChatItem: {
|
||||||
ChatItemCount: (count: number) => `${count} 条对话`,
|
ChatItemCount: (count: number) => `${count} 条对话`,
|
||||||
|
@ -46,6 +56,8 @@ const cn = {
|
||||||
FullScreen: "全屏",
|
FullScreen: "全屏",
|
||||||
RefreshTitle: "刷新标题",
|
RefreshTitle: "刷新标题",
|
||||||
RefreshToast: "已发送刷新标题请求",
|
RefreshToast: "已发送刷新标题请求",
|
||||||
|
Speech: "朗读",
|
||||||
|
StopSpeech: "停止",
|
||||||
},
|
},
|
||||||
Commands: {
|
Commands: {
|
||||||
new: "新建聊天",
|
new: "新建聊天",
|
||||||
|
@ -53,6 +65,7 @@ const cn = {
|
||||||
next: "下一个聊天",
|
next: "下一个聊天",
|
||||||
prev: "上一个聊天",
|
prev: "上一个聊天",
|
||||||
clear: "清除上下文",
|
clear: "清除上下文",
|
||||||
|
fork: "复制聊天",
|
||||||
del: "删除聊天",
|
del: "删除聊天",
|
||||||
},
|
},
|
||||||
InputActions: {
|
InputActions: {
|
||||||
|
@ -79,6 +92,8 @@ const cn = {
|
||||||
return inputHints + ",/ 触发补全,: 触发命令";
|
return inputHints + ",/ 触发补全,: 触发命令";
|
||||||
},
|
},
|
||||||
Send: "发送",
|
Send: "发送",
|
||||||
|
StartSpeak: "说话",
|
||||||
|
StopSpeak: "停止",
|
||||||
Config: {
|
Config: {
|
||||||
Reset: "清除记忆",
|
Reset: "清除记忆",
|
||||||
SaveAs: "存为面具",
|
SaveAs: "存为面具",
|
||||||
|
@ -293,6 +308,13 @@ const cn = {
|
||||||
},
|
},
|
||||||
|
|
||||||
Access: {
|
Access: {
|
||||||
|
SaasStart: {
|
||||||
|
Title: "使用 NextChat AI",
|
||||||
|
Label: "(性价比最高的方案)",
|
||||||
|
SubTitle:
|
||||||
|
"由 NextChat 官方维护, 零配置开箱即用,支持 OpenAI o1, GPT-4o, Claude-3.5 等最新大模型",
|
||||||
|
ChatNow: "立刻对话",
|
||||||
|
},
|
||||||
AccessCode: {
|
AccessCode: {
|
||||||
Title: "访问密码",
|
Title: "访问密码",
|
||||||
SubTitle: "管理员已开启加密访问",
|
SubTitle: "管理员已开启加密访问",
|
||||||
|
@ -356,7 +378,7 @@ const cn = {
|
||||||
ApiKey: {
|
ApiKey: {
|
||||||
Title: "API 密钥",
|
Title: "API 密钥",
|
||||||
SubTitle: "从 Google AI 获取您的 API 密钥",
|
SubTitle: "从 Google AI 获取您的 API 密钥",
|
||||||
Placeholder: "输入您的 Google AI Studio API 密钥",
|
Placeholder: "Google AI API KEY",
|
||||||
},
|
},
|
||||||
|
|
||||||
Endpoint: {
|
Endpoint: {
|
||||||
|
@ -496,6 +518,26 @@ const cn = {
|
||||||
Title: "频率惩罚度 (frequency_penalty)",
|
Title: "频率惩罚度 (frequency_penalty)",
|
||||||
SubTitle: "值越大,越有可能降低重复字词",
|
SubTitle: "值越大,越有可能降低重复字词",
|
||||||
},
|
},
|
||||||
|
TTS: {
|
||||||
|
Enable: {
|
||||||
|
Title: "启用文本转语音",
|
||||||
|
SubTitle: "启用文本生成语音服务",
|
||||||
|
},
|
||||||
|
Autoplay: {
|
||||||
|
Title: "启用自动朗读",
|
||||||
|
SubTitle: "自动生成语音并播放,需先开启文本转语音开关",
|
||||||
|
},
|
||||||
|
Model: "模型",
|
||||||
|
Engine: "转换引擎",
|
||||||
|
Voice: {
|
||||||
|
Title: "声音",
|
||||||
|
SubTitle: "生成语音时使用的声音",
|
||||||
|
},
|
||||||
|
Speed: {
|
||||||
|
Title: "速度",
|
||||||
|
SubTitle: "生成语音的速度",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Store: {
|
Store: {
|
||||||
DefaultTopic: "新的聊天",
|
DefaultTopic: "新的聊天",
|
||||||
|
|
|
@ -1,15 +1,21 @@
|
||||||
import { SubmitKey } from "../store/config";
|
import { SubmitKey } from "../store/config";
|
||||||
import type { PartialLocaleType } from "./index";
|
import type { PartialLocaleType } from "./index";
|
||||||
import { getClientConfig } from "../config/client";
|
import { getClientConfig } from "../config/client";
|
||||||
|
import { SAAS_CHAT_UTM_URL } from "@/app/constant";
|
||||||
const isApp = !!getClientConfig()?.isApp;
|
const isApp = !!getClientConfig()?.isApp;
|
||||||
|
|
||||||
const cs: PartialLocaleType = {
|
const cs: PartialLocaleType = {
|
||||||
WIP: "V přípravě...",
|
WIP: "V přípravě...",
|
||||||
Error: {
|
Error: {
|
||||||
Unauthorized: isApp
|
Unauthorized: isApp
|
||||||
? "Byl zjištěn neplatný API Key, prosím přejděte na stránku [Nastavení](/#/settings) a zkontrolujte, zda je API Key správně nakonfigurován."
|
? `😆 Rozhovor narazil na nějaké problémy, nebojte se:
|
||||||
: "Heslo je nesprávné nebo prázdné, prosím přejděte na stránku [Přihlášení](/#/auth) a zadejte správné heslo, nebo na stránku [Nastavení](/#/settings) a zadejte svůj vlastní OpenAI API Key.",
|
\\ 1️⃣ Pokud chcete začít bez konfigurace, [klikněte sem pro okamžitý začátek chatu 🚀](${SAAS_CHAT_UTM_URL})
|
||||||
|
\\ 2️⃣ Pokud chcete využít své vlastní zdroje OpenAI, klikněte [sem](/#/settings) a upravte nastavení ⚙️`
|
||||||
|
: `😆 Rozhovor narazil na nějaké problémy, nebojte se:
|
||||||
|
\ 1️⃣ Pokud chcete začít bez konfigurace, [klikněte sem pro okamžitý začátek chatu 🚀](${SAAS_CHAT_UTM_URL})
|
||||||
|
\ 2️⃣ Pokud používáte verzi soukromého nasazení, klikněte [sem](/#/auth) a zadejte přístupový klíč 🔑
|
||||||
|
\ 3️⃣ Pokud chcete využít své vlastní zdroje OpenAI, klikněte [sem](/#/settings) a upravte nastavení ⚙️
|
||||||
|
`,
|
||||||
},
|
},
|
||||||
Auth: {
|
Auth: {
|
||||||
Title: "Potřebné heslo",
|
Title: "Potřebné heslo",
|
||||||
|
@ -18,6 +24,10 @@ const cs: PartialLocaleType = {
|
||||||
Input: "Zadejte přístupový kód zde",
|
Input: "Zadejte přístupový kód zde",
|
||||||
Confirm: "Potvrdit",
|
Confirm: "Potvrdit",
|
||||||
Later: "Později",
|
Later: "Později",
|
||||||
|
Return: "Návrat",
|
||||||
|
SaasTips: "Konfigurace je příliš složitá, chci okamžitě začít používat",
|
||||||
|
TopTips:
|
||||||
|
"🥳 Uvítací nabídka NextChat AI, okamžitě odemkněte OpenAI o1, GPT-4o, Claude-3.5 a nejnovější velké modely",
|
||||||
},
|
},
|
||||||
ChatItem: {
|
ChatItem: {
|
||||||
ChatItemCount: (count: number) => `${count} konverzací`,
|
ChatItemCount: (count: number) => `${count} konverzací`,
|
||||||
|
@ -284,6 +294,14 @@ const cs: PartialLocaleType = {
|
||||||
},
|
},
|
||||||
|
|
||||||
Access: {
|
Access: {
|
||||||
|
SaasStart: {
|
||||||
|
Title: "Použití NextChat AI",
|
||||||
|
Label: "(Nejlepší nákladově efektivní řešení)",
|
||||||
|
SubTitle:
|
||||||
|
"Oficiálně udržováno NextChat, připraveno k použití bez konfigurace, podporuje nejnovější velké modely jako OpenAI o1, GPT-4o, Claude-3.5",
|
||||||
|
ChatNow: "Začněte chatovat nyní",
|
||||||
|
},
|
||||||
|
|
||||||
AccessCode: {
|
AccessCode: {
|
||||||
Title: "Přístupový kód",
|
Title: "Přístupový kód",
|
||||||
SubTitle: "Administrátor aktivoval šifrovaný přístup",
|
SubTitle: "Administrátor aktivoval šifrovaný přístup",
|
||||||
|
|
|
@ -1,15 +1,21 @@
|
||||||
import { SubmitKey } from "../store/config";
|
import { SubmitKey } from "../store/config";
|
||||||
import type { PartialLocaleType } from "./index";
|
import type { PartialLocaleType } from "./index";
|
||||||
import { getClientConfig } from "../config/client";
|
import { getClientConfig } from "../config/client";
|
||||||
|
import { SAAS_CHAT_UTM_URL } from "@/app/constant";
|
||||||
const isApp = !!getClientConfig()?.isApp;
|
const isApp = !!getClientConfig()?.isApp;
|
||||||
|
|
||||||
const de: PartialLocaleType = {
|
const de: PartialLocaleType = {
|
||||||
WIP: "In Bearbeitung...",
|
WIP: "In Bearbeitung...",
|
||||||
Error: {
|
Error: {
|
||||||
Unauthorized: isApp
|
Unauthorized: isApp
|
||||||
? "Ungültiger API-Schlüssel erkannt. Bitte gehen Sie zur [Einstellungen](/#/settings) Seite, um zu überprüfen, ob der API-Schlüssel korrekt konfiguriert ist."
|
? `😆 Das Gespräch hatte einige Probleme, keine Sorge:
|
||||||
: "Das Passwort ist falsch oder leer. Bitte gehen Sie zur [Login](/#/auth) Seite, um das richtige Passwort einzugeben, oder fügen Sie Ihren OpenAI API-Schlüssel auf der [Einstellungen](/#/settings) Seite hinzu.",
|
\\ 1️⃣ Wenn du ohne Konfiguration sofort starten möchtest, [klicke hier, um sofort zu chatten 🚀](${SAAS_CHAT_UTM_URL})
|
||||||
|
\\ 2️⃣ Wenn du deine eigenen OpenAI-Ressourcen verwenden möchtest, klicke [hier](/#/settings), um die Einstellungen zu ändern ⚙️`
|
||||||
|
: `😆 Das Gespräch hatte einige Probleme, keine Sorge:
|
||||||
|
\ 1️⃣ Wenn du ohne Konfiguration sofort starten möchtest, [klicke hier, um sofort zu chatten 🚀](${SAAS_CHAT_UTM_URL})
|
||||||
|
\ 2️⃣ Wenn du eine private Bereitstellung verwendest, klicke [hier](/#/auth), um den Zugriffsschlüssel einzugeben 🔑
|
||||||
|
\ 3️⃣ Wenn du deine eigenen OpenAI-Ressourcen verwenden möchtest, klicke [hier](/#/settings), um die Einstellungen zu ändern ⚙️
|
||||||
|
`,
|
||||||
},
|
},
|
||||||
Auth: {
|
Auth: {
|
||||||
Title: "Passwort erforderlich",
|
Title: "Passwort erforderlich",
|
||||||
|
@ -18,6 +24,11 @@ const de: PartialLocaleType = {
|
||||||
Input: "Geben Sie hier den Zugangscode ein",
|
Input: "Geben Sie hier den Zugangscode ein",
|
||||||
Confirm: "Bestätigen",
|
Confirm: "Bestätigen",
|
||||||
Later: "Später",
|
Later: "Später",
|
||||||
|
Return: "Zurück",
|
||||||
|
SaasTips:
|
||||||
|
"Die Konfiguration ist zu kompliziert, ich möchte es sofort nutzen",
|
||||||
|
TopTips:
|
||||||
|
"🥳 NextChat AI Einführungsangebot, schalte jetzt OpenAI o1, GPT-4o, Claude-3.5 und die neuesten großen Modelle frei",
|
||||||
},
|
},
|
||||||
ChatItem: {
|
ChatItem: {
|
||||||
ChatItemCount: (count: number) => `${count} Gespräche`,
|
ChatItemCount: (count: number) => `${count} Gespräche`,
|
||||||
|
@ -291,6 +302,14 @@ const de: PartialLocaleType = {
|
||||||
},
|
},
|
||||||
|
|
||||||
Access: {
|
Access: {
|
||||||
|
SaasStart: {
|
||||||
|
Title: "NextChat AI verwenden",
|
||||||
|
Label: "(Die kosteneffektivste Lösung)",
|
||||||
|
SubTitle:
|
||||||
|
"Offiziell von NextChat verwaltet, sofort einsatzbereit ohne Konfiguration, unterstützt die neuesten großen Modelle wie OpenAI o1, GPT-4o und Claude-3.5",
|
||||||
|
ChatNow: "Jetzt chatten",
|
||||||
|
},
|
||||||
|
|
||||||
AccessCode: {
|
AccessCode: {
|
||||||
Title: "Zugangscode",
|
Title: "Zugangscode",
|
||||||
SubTitle:
|
SubTitle:
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { getClientConfig } from "../config/client";
|
import { getClientConfig } from "../config/client";
|
||||||
import { SubmitKey } from "../store/config";
|
import { SubmitKey } from "../store/config";
|
||||||
import { LocaleType } from "./index";
|
import { LocaleType } from "./index";
|
||||||
|
import { SAAS_CHAT_UTM_URL } from "@/app/constant";
|
||||||
// if you are adding a new translation, please use PartialLocaleType instead of LocaleType
|
// if you are adding a new translation, please use PartialLocaleType instead of LocaleType
|
||||||
|
|
||||||
const isApp = !!getClientConfig()?.isApp;
|
const isApp = !!getClientConfig()?.isApp;
|
||||||
|
@ -9,16 +9,26 @@ const en: LocaleType = {
|
||||||
WIP: "Coming Soon...",
|
WIP: "Coming Soon...",
|
||||||
Error: {
|
Error: {
|
||||||
Unauthorized: isApp
|
Unauthorized: isApp
|
||||||
? "Invalid API Key, please check it in [Settings](/#/settings) page."
|
? `😆 Oops, there's an issue. No worries:
|
||||||
: "Unauthorized access, please enter access code in [auth](/#/auth) page, or enter your OpenAI API Key.",
|
\\ 1️⃣ New here? [Click to start chatting now 🚀](${SAAS_CHAT_UTM_URL})
|
||||||
|
\\ 2️⃣ Want to use your own OpenAI resources? [Click here](/#/settings) to change settings ⚙️`
|
||||||
|
: `😆 Oops, there's an issue. Let's fix it:
|
||||||
|
\ 1️⃣ New here? [Click to start chatting now 🚀](${SAAS_CHAT_UTM_URL})
|
||||||
|
\ 2️⃣ Using a private setup? [Click here](/#/auth) to enter your key 🔑
|
||||||
|
\ 3️⃣ Want to use your own OpenAI resources? [Click here](/#/settings) to change settings ⚙️
|
||||||
|
`,
|
||||||
},
|
},
|
||||||
Auth: {
|
Auth: {
|
||||||
|
Return: "Return",
|
||||||
Title: "Need Access Code",
|
Title: "Need Access Code",
|
||||||
Tips: "Please enter access code below",
|
Tips: "Please enter access code below",
|
||||||
SubTips: "Or enter your OpenAI or Google API Key",
|
SubTips: "Or enter your OpenAI or Google API Key",
|
||||||
Input: "access code",
|
Input: "access code",
|
||||||
Confirm: "Confirm",
|
Confirm: "Confirm",
|
||||||
Later: "Later",
|
Later: "Later",
|
||||||
|
SaasTips: "Too Complex, Use Immediately Now",
|
||||||
|
TopTips:
|
||||||
|
"🥳 NextChat AI launch promotion: Instantly unlock the latest models like OpenAI o1, GPT-4o, Claude-3.5!",
|
||||||
},
|
},
|
||||||
ChatItem: {
|
ChatItem: {
|
||||||
ChatItemCount: (count: number) => `${count} messages`,
|
ChatItemCount: (count: number) => `${count} messages`,
|
||||||
|
@ -47,6 +57,8 @@ const en: LocaleType = {
|
||||||
FullScreen: "FullScreen",
|
FullScreen: "FullScreen",
|
||||||
RefreshTitle: "Refresh Title",
|
RefreshTitle: "Refresh Title",
|
||||||
RefreshToast: "Title refresh request sent",
|
RefreshToast: "Title refresh request sent",
|
||||||
|
Speech: "Play",
|
||||||
|
StopSpeech: "Stop",
|
||||||
},
|
},
|
||||||
Commands: {
|
Commands: {
|
||||||
new: "Start a new chat",
|
new: "Start a new chat",
|
||||||
|
@ -54,6 +66,7 @@ const en: LocaleType = {
|
||||||
next: "Next Chat",
|
next: "Next Chat",
|
||||||
prev: "Previous Chat",
|
prev: "Previous Chat",
|
||||||
clear: "Clear Context",
|
clear: "Clear Context",
|
||||||
|
fork: "Copy Chat",
|
||||||
del: "Delete Chat",
|
del: "Delete Chat",
|
||||||
},
|
},
|
||||||
InputActions: {
|
InputActions: {
|
||||||
|
@ -80,6 +93,8 @@ const en: LocaleType = {
|
||||||
return inputHints + ", / to search prompts, : to use commands";
|
return inputHints + ", / to search prompts, : to use commands";
|
||||||
},
|
},
|
||||||
Send: "Send",
|
Send: "Send",
|
||||||
|
StartSpeak: "Start Speak",
|
||||||
|
StopSpeak: "Stop Speak",
|
||||||
Config: {
|
Config: {
|
||||||
Reset: "Reset to Default",
|
Reset: "Reset to Default",
|
||||||
SaveAs: "Save as Mask",
|
SaveAs: "Save as Mask",
|
||||||
|
@ -296,6 +311,14 @@ const en: LocaleType = {
|
||||||
NoAccess: "Enter API Key to check balance",
|
NoAccess: "Enter API Key to check balance",
|
||||||
},
|
},
|
||||||
Access: {
|
Access: {
|
||||||
|
SaasStart: {
|
||||||
|
Title: "Use NextChat AI",
|
||||||
|
Label: " (Most Cost-Effective Option)",
|
||||||
|
SubTitle:
|
||||||
|
"Maintained by NextChat, zero setup needed, unlock OpenAI o1, GPT-4o," +
|
||||||
|
" Claude-3.5 and more",
|
||||||
|
ChatNow: "Start Now",
|
||||||
|
},
|
||||||
AccessCode: {
|
AccessCode: {
|
||||||
Title: "Access Code",
|
Title: "Access Code",
|
||||||
SubTitle: "Access control Enabled",
|
SubTitle: "Access control Enabled",
|
||||||
|
@ -456,7 +479,7 @@ const en: LocaleType = {
|
||||||
ApiKey: {
|
ApiKey: {
|
||||||
Title: "API Key",
|
Title: "API Key",
|
||||||
SubTitle: "Obtain your API Key from Google AI",
|
SubTitle: "Obtain your API Key from Google AI",
|
||||||
Placeholder: "Enter your Google AI Studio API Key",
|
Placeholder: "Google AI API Key",
|
||||||
},
|
},
|
||||||
|
|
||||||
Endpoint: {
|
Endpoint: {
|
||||||
|
@ -502,6 +525,27 @@ const en: LocaleType = {
|
||||||
SubTitle:
|
SubTitle:
|
||||||
"A larger value decreasing the likelihood to repeat the same line",
|
"A larger value decreasing the likelihood to repeat the same line",
|
||||||
},
|
},
|
||||||
|
TTS: {
|
||||||
|
Enable: {
|
||||||
|
Title: "Enable TTS",
|
||||||
|
SubTitle: "Enable text-to-speech service",
|
||||||
|
},
|
||||||
|
Autoplay: {
|
||||||
|
Title: "Enable Autoplay",
|
||||||
|
SubTitle:
|
||||||
|
"Automatically generate speech and play, you need to enable the text-to-speech switch first",
|
||||||
|
},
|
||||||
|
Model: "Model",
|
||||||
|
Voice: {
|
||||||
|
Title: "Voice",
|
||||||
|
SubTitle: "The voice to use when generating the audio",
|
||||||
|
},
|
||||||
|
Speed: {
|
||||||
|
Title: "Speed",
|
||||||
|
SubTitle: "The speed of the generated audio",
|
||||||
|
},
|
||||||
|
Engine: "TTS Engine",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Store: {
|
Store: {
|
||||||
DefaultTopic: "New Conversation",
|
DefaultTopic: "New Conversation",
|
||||||
|
|
|
@ -1,15 +1,21 @@
|
||||||
import { SubmitKey } from "../store/config";
|
import { SubmitKey } from "../store/config";
|
||||||
import type { PartialLocaleType } from "./index";
|
import type { PartialLocaleType } from "./index";
|
||||||
import { getClientConfig } from "../config/client";
|
import { getClientConfig } from "../config/client";
|
||||||
|
import { SAAS_CHAT_UTM_URL } from "@/app/constant";
|
||||||
const isApp = !!getClientConfig()?.isApp;
|
const isApp = !!getClientConfig()?.isApp;
|
||||||
|
|
||||||
const es: PartialLocaleType = {
|
const es: PartialLocaleType = {
|
||||||
WIP: "En construcción...",
|
WIP: "En construcción...",
|
||||||
Error: {
|
Error: {
|
||||||
Unauthorized: isApp
|
Unauthorized: isApp
|
||||||
? "Se detectó una clave API inválida. Por favor, ve a la página de [Configuración](/#/settings) para verificar si la clave API está configurada correctamente."
|
? `😆 La conversación encontró algunos problemas, no te preocupes:
|
||||||
: "La contraseña de acceso es incorrecta o está vacía. Por favor, ve a la página de [Iniciar sesión](/#/auth) para ingresar la contraseña correcta, o en la página de [Configuración](/#/settings) para introducir tu propia clave API de OpenAI.",
|
\\ 1️⃣ Si deseas comenzar sin configuración, [haz clic aquí para empezar a chatear inmediatamente 🚀](${SAAS_CHAT_UTM_URL})
|
||||||
|
\\ 2️⃣ Si deseas usar tus propios recursos de OpenAI, haz clic [aquí](/#/settings) para modificar la configuración ⚙️`
|
||||||
|
: `😆 La conversación encontró algunos problemas, no te preocupes:
|
||||||
|
\ 1️⃣ Si deseas comenzar sin configuración, [haz clic aquí para empezar a chatear inmediatamente 🚀](${SAAS_CHAT_UTM_URL})
|
||||||
|
\ 2️⃣ Si estás utilizando una versión de implementación privada, haz clic [aquí](/#/auth) para ingresar la clave de acceso 🔑
|
||||||
|
\ 3️⃣ Si deseas usar tus propios recursos de OpenAI, haz clic [aquí](/#/settings) para modificar la configuración ⚙️
|
||||||
|
`,
|
||||||
},
|
},
|
||||||
Auth: {
|
Auth: {
|
||||||
Title: "Se requiere contraseña",
|
Title: "Se requiere contraseña",
|
||||||
|
@ -18,6 +24,11 @@ const es: PartialLocaleType = {
|
||||||
Input: "Introduce el código de acceso aquí",
|
Input: "Introduce el código de acceso aquí",
|
||||||
Confirm: "Confirmar",
|
Confirm: "Confirmar",
|
||||||
Later: "Más tarde",
|
Later: "Más tarde",
|
||||||
|
Return: "Regresar",
|
||||||
|
SaasTips:
|
||||||
|
"La configuración es demasiado complicada, quiero usarlo de inmediato",
|
||||||
|
TopTips:
|
||||||
|
"🥳 Oferta de lanzamiento de NextChat AI, desbloquea OpenAI o1, GPT-4o, Claude-3.5 y los últimos grandes modelos",
|
||||||
},
|
},
|
||||||
ChatItem: {
|
ChatItem: {
|
||||||
ChatItemCount: (count: number) => `${count} conversaciones`,
|
ChatItemCount: (count: number) => `${count} conversaciones`,
|
||||||
|
@ -294,6 +305,14 @@ const es: PartialLocaleType = {
|
||||||
},
|
},
|
||||||
|
|
||||||
Access: {
|
Access: {
|
||||||
|
SaasStart: {
|
||||||
|
Title: "Use NextChat AI",
|
||||||
|
Label: "(The most cost-effective solution)",
|
||||||
|
SubTitle:
|
||||||
|
"Officially maintained by NextChat, zero configuration ready to use, supports the latest large models like OpenAI o1, GPT-4o, and Claude-3.5",
|
||||||
|
ChatNow: "Chat Now",
|
||||||
|
},
|
||||||
|
|
||||||
AccessCode: {
|
AccessCode: {
|
||||||
Title: "Contraseña de acceso",
|
Title: "Contraseña de acceso",
|
||||||
SubTitle: "El administrador ha habilitado el acceso encriptado",
|
SubTitle: "El administrador ha habilitado el acceso encriptado",
|
||||||
|
|
|
@ -1,15 +1,21 @@
|
||||||
import { SubmitKey } from "../store/config";
|
import { SubmitKey } from "../store/config";
|
||||||
import type { PartialLocaleType } from "./index";
|
import type { PartialLocaleType } from "./index";
|
||||||
import { getClientConfig } from "../config/client";
|
import { getClientConfig } from "../config/client";
|
||||||
|
import { SAAS_CHAT_UTM_URL } from "@/app/constant";
|
||||||
const isApp = !!getClientConfig()?.isApp;
|
const isApp = !!getClientConfig()?.isApp;
|
||||||
|
|
||||||
const fr: PartialLocaleType = {
|
const fr: PartialLocaleType = {
|
||||||
WIP: "Prochainement...",
|
WIP: "Prochainement...",
|
||||||
Error: {
|
Error: {
|
||||||
Unauthorized: isApp
|
Unauthorized: isApp
|
||||||
? "Clé API invalide détectée. Veuillez vérifier si la clé API est correctement configurée dans la page [Paramètres](/#/settings)."
|
? `😆 La conversation a rencontré quelques problèmes, pas de panique :
|
||||||
: "Le mot de passe d'accès est incorrect ou manquant. Veuillez entrer le mot de passe d'accès correct sur la page [Connexion](/#/auth) ou entrer votre propre clé API OpenAI sur la page [Paramètres](/#/settings).",
|
\\ 1️⃣ Si vous souhaitez commencer sans configuration, [cliquez ici pour démarrer la conversation immédiatement 🚀](${SAAS_CHAT_UTM_URL})
|
||||||
|
\\ 2️⃣ Si vous souhaitez utiliser vos propres ressources OpenAI, cliquez [ici](/#/settings) pour modifier les paramètres ⚙️`
|
||||||
|
: `😆 La conversation a rencontré quelques problèmes, pas de panique :
|
||||||
|
\ 1️⃣ Si vous souhaitez commencer sans configuration, [cliquez ici pour démarrer la conversation immédiatement 🚀](${SAAS_CHAT_UTM_URL})
|
||||||
|
\ 2️⃣ Si vous utilisez une version déployée privée, cliquez [ici](/#/auth) pour entrer la clé d'accès 🔑
|
||||||
|
\ 3️⃣ Si vous souhaitez utiliser vos propres ressources OpenAI, cliquez [ici](/#/settings) pour modifier les paramètres ⚙️
|
||||||
|
`,
|
||||||
},
|
},
|
||||||
Auth: {
|
Auth: {
|
||||||
Title: "Mot de passe requis",
|
Title: "Mot de passe requis",
|
||||||
|
@ -18,6 +24,11 @@ const fr: PartialLocaleType = {
|
||||||
Input: "Entrez le code d'accès ici",
|
Input: "Entrez le code d'accès ici",
|
||||||
Confirm: "Confirmer",
|
Confirm: "Confirmer",
|
||||||
Later: "Plus tard",
|
Later: "Plus tard",
|
||||||
|
Return: "Retour",
|
||||||
|
SaasTips:
|
||||||
|
"La configuration est trop compliquée, je veux l'utiliser immédiatement",
|
||||||
|
TopTips:
|
||||||
|
"🥳 Offre de lancement NextChat AI, débloquez OpenAI o1, GPT-4o, Claude-3.5 et les derniers grands modèles",
|
||||||
},
|
},
|
||||||
ChatItem: {
|
ChatItem: {
|
||||||
ChatItemCount: (count: number) => `${count} conversations`,
|
ChatItemCount: (count: number) => `${count} conversations`,
|
||||||
|
@ -294,6 +305,14 @@ const fr: PartialLocaleType = {
|
||||||
},
|
},
|
||||||
|
|
||||||
Access: {
|
Access: {
|
||||||
|
SaasStart: {
|
||||||
|
Title: "Utiliser NextChat AI",
|
||||||
|
Label: "(La solution la plus rentable)",
|
||||||
|
SubTitle:
|
||||||
|
"Officiellement maintenu par NextChat, prêt à l'emploi sans configuration, prend en charge les derniers grands modèles comme OpenAI o1, GPT-4o et Claude-3.5",
|
||||||
|
ChatNow: "Discuter maintenant",
|
||||||
|
},
|
||||||
|
|
||||||
AccessCode: {
|
AccessCode: {
|
||||||
Title: "Mot de passe d'accès",
|
Title: "Mot de passe d'accès",
|
||||||
SubTitle: "L'administrateur a activé l'accès sécurisé",
|
SubTitle: "L'administrateur a activé l'accès sécurisé",
|
||||||
|
|
|
@ -1,15 +1,21 @@
|
||||||
import { SubmitKey } from "../store/config";
|
import { SubmitKey } from "../store/config";
|
||||||
import type { PartialLocaleType } from "./index";
|
import type { PartialLocaleType } from "./index";
|
||||||
import { getClientConfig } from "../config/client";
|
import { getClientConfig } from "../config/client";
|
||||||
|
import { SAAS_CHAT_UTM_URL } from "@/app/constant";
|
||||||
const isApp = !!getClientConfig()?.isApp;
|
const isApp = !!getClientConfig()?.isApp;
|
||||||
|
|
||||||
const id: PartialLocaleType = {
|
const id: PartialLocaleType = {
|
||||||
WIP: "Coming Soon...",
|
WIP: "Coming Soon...",
|
||||||
Error: {
|
Error: {
|
||||||
Unauthorized: isApp
|
Unauthorized: isApp
|
||||||
? "API Key tidak valid terdeteksi, silakan periksa apakah API Key telah dikonfigurasi dengan benar di halaman [Pengaturan](/#/settings)."
|
? `😆 Percakapan mengalami beberapa masalah, tidak perlu khawatir:
|
||||||
: "Kata sandi akses tidak benar atau kosong, silakan masukkan kata sandi akses yang benar di halaman [Masuk](/#/auth), atau masukkan OpenAI API Key Anda di halaman [Pengaturan](/#/settings).",
|
\\ 1️⃣ Jika Anda ingin memulai tanpa konfigurasi, [klik di sini untuk mulai mengobrol segera 🚀](${SAAS_CHAT_UTM_URL})
|
||||||
|
\\ 2️⃣ Jika Anda ingin menggunakan sumber daya OpenAI Anda sendiri, klik [di sini](/#/settings) untuk mengubah pengaturan ⚙️`
|
||||||
|
: `😆 Percakapan mengalami beberapa masalah, tidak perlu khawatir:
|
||||||
|
\ 1️⃣ Jika Anda ingin memulai tanpa konfigurasi, [klik di sini untuk mulai mengobrol segera 🚀](${SAAS_CHAT_UTM_URL})
|
||||||
|
\ 2️⃣ Jika Anda menggunakan versi penyebaran pribadi, klik [di sini](/#/auth) untuk memasukkan kunci akses 🔑
|
||||||
|
\ 3️⃣ Jika Anda ingin menggunakan sumber daya OpenAI Anda sendiri, klik [di sini](/#/settings) untuk mengubah pengaturan ⚙️
|
||||||
|
`,
|
||||||
},
|
},
|
||||||
Auth: {
|
Auth: {
|
||||||
Title: "Kebutuhan Kata Sandi",
|
Title: "Kebutuhan Kata Sandi",
|
||||||
|
@ -18,6 +24,10 @@ const id: PartialLocaleType = {
|
||||||
Input: "Masukkan kode akses di sini",
|
Input: "Masukkan kode akses di sini",
|
||||||
Confirm: "Konfirmasi",
|
Confirm: "Konfirmasi",
|
||||||
Later: "Nanti",
|
Later: "Nanti",
|
||||||
|
Return: "Kembali",
|
||||||
|
SaasTips: "Konfigurasi terlalu rumit, saya ingin menggunakannya segera",
|
||||||
|
TopTips:
|
||||||
|
"🥳 Penawaran Peluncuran NextChat AI, buka OpenAI o1, GPT-4o, Claude-3.5 dan model besar terbaru sekarang",
|
||||||
},
|
},
|
||||||
ChatItem: {
|
ChatItem: {
|
||||||
ChatItemCount: (count: number) => `${count} percakapan`,
|
ChatItemCount: (count: number) => `${count} percakapan`,
|
||||||
|
@ -285,6 +295,14 @@ const id: PartialLocaleType = {
|
||||||
},
|
},
|
||||||
|
|
||||||
Access: {
|
Access: {
|
||||||
|
SaasStart: {
|
||||||
|
Title: "Gunakan NextChat AI",
|
||||||
|
Label: "(Solusi paling hemat biaya)",
|
||||||
|
SubTitle:
|
||||||
|
"Dikelola secara resmi oleh NextChat, siap digunakan tanpa konfigurasi, mendukung model besar terbaru seperti OpenAI o1, GPT-4o, dan Claude-3.5",
|
||||||
|
ChatNow: "Chat Sekarang",
|
||||||
|
},
|
||||||
|
|
||||||
AccessCode: {
|
AccessCode: {
|
||||||
Title: "Kata Sandi Akses",
|
Title: "Kata Sandi Akses",
|
||||||
SubTitle: "Administrator telah mengaktifkan akses terenkripsi",
|
SubTitle: "Administrator telah mengaktifkan akses terenkripsi",
|
||||||
|
|
|
@ -134,3 +134,34 @@ export function getISOLang() {
|
||||||
const lang = getLang();
|
const lang = getLang();
|
||||||
return isoLangString[lang] ?? lang;
|
return isoLangString[lang] ?? lang;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DEFAULT_STT_LANG = "zh-CN";
|
||||||
|
export const STT_LANG_MAP: Record<Lang, string> = {
|
||||||
|
cn: "zh-CN",
|
||||||
|
en: "en-US",
|
||||||
|
pt: "pt-BR",
|
||||||
|
tw: "zh-TW",
|
||||||
|
jp: "ja-JP",
|
||||||
|
ko: "ko-KR",
|
||||||
|
id: "id-ID",
|
||||||
|
fr: "fr-FR",
|
||||||
|
es: "es-ES",
|
||||||
|
it: "it-IT",
|
||||||
|
tr: "tr-TR",
|
||||||
|
de: "de-DE",
|
||||||
|
vi: "vi-VN",
|
||||||
|
ru: "ru-RU",
|
||||||
|
cs: "cs-CZ",
|
||||||
|
no: "no-NO",
|
||||||
|
ar: "ar-SA",
|
||||||
|
bn: "bn-BD",
|
||||||
|
sk: "sk-SK",
|
||||||
|
};
|
||||||
|
|
||||||
|
export function getSTTLang(): string {
|
||||||
|
try {
|
||||||
|
return STT_LANG_MAP[getLang()];
|
||||||
|
} catch {
|
||||||
|
return DEFAULT_STT_LANG;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,15 +1,21 @@
|
||||||
import { SubmitKey } from "../store/config";
|
import { SubmitKey } from "../store/config";
|
||||||
import type { PartialLocaleType } from "./index";
|
import type { PartialLocaleType } from "./index";
|
||||||
import { getClientConfig } from "../config/client";
|
import { getClientConfig } from "../config/client";
|
||||||
|
import { SAAS_CHAT_UTM_URL } from "@/app/constant";
|
||||||
const isApp = !!getClientConfig()?.isApp;
|
const isApp = !!getClientConfig()?.isApp;
|
||||||
|
|
||||||
const it: PartialLocaleType = {
|
const it: PartialLocaleType = {
|
||||||
WIP: "Work in progress...",
|
WIP: "Work in progress...",
|
||||||
Error: {
|
Error: {
|
||||||
Unauthorized: isApp
|
Unauthorized: isApp
|
||||||
? "API Key non valido rilevato. Vai alla pagina [Impostazioni](/#/settings) per controllare se l'API Key è configurata correttamente."
|
? `😆 La conversazione ha incontrato alcuni problemi, non preoccuparti:
|
||||||
: "La password di accesso è errata o mancante. Vai alla pagina [Login](/#/auth) per inserire la password corretta o inserisci la tua API Key OpenAI nella pagina [Impostazioni](/#/settings).",
|
\\ 1️⃣ Se vuoi iniziare senza configurazione, [clicca qui per iniziare a chattare immediatamente 🚀](${SAAS_CHAT_UTM_URL})
|
||||||
|
\\ 2️⃣ Se vuoi utilizzare le tue risorse OpenAI, clicca [qui](/#/settings) per modificare le impostazioni ⚙️`
|
||||||
|
: `😆 La conversazione ha incontrato alcuni problemi, non preoccuparti:
|
||||||
|
\ 1️⃣ Se vuoi iniziare senza configurazione, [clicca qui per iniziare a chattare immediatamente 🚀](${SAAS_CHAT_UTM_URL})
|
||||||
|
\ 2️⃣ Se stai utilizzando una versione di distribuzione privata, clicca [qui](/#/auth) per inserire la chiave di accesso 🔑
|
||||||
|
\ 3️⃣ Se vuoi utilizzare le tue risorse OpenAI, clicca [qui](/#/settings) per modificare le impostazioni ⚙️
|
||||||
|
`,
|
||||||
},
|
},
|
||||||
Auth: {
|
Auth: {
|
||||||
Title: "Password richiesta",
|
Title: "Password richiesta",
|
||||||
|
@ -18,6 +24,11 @@ const it: PartialLocaleType = {
|
||||||
Input: "Inserisci il codice di accesso qui",
|
Input: "Inserisci il codice di accesso qui",
|
||||||
Confirm: "Conferma",
|
Confirm: "Conferma",
|
||||||
Later: "Più tardi",
|
Later: "Più tardi",
|
||||||
|
Return: "Ritorna",
|
||||||
|
SaasTips:
|
||||||
|
"La configurazione è troppo complicata, voglio usarlo immediatamente",
|
||||||
|
TopTips:
|
||||||
|
"🥳 Offerta di lancio NextChat AI, sblocca OpenAI o1, GPT-4o, Claude-3.5 e i più recenti modelli di grandi dimensioni",
|
||||||
},
|
},
|
||||||
ChatItem: {
|
ChatItem: {
|
||||||
ChatItemCount: (count: number) => `${count} conversazioni`,
|
ChatItemCount: (count: number) => `${count} conversazioni`,
|
||||||
|
@ -295,6 +306,14 @@ const it: PartialLocaleType = {
|
||||||
},
|
},
|
||||||
|
|
||||||
Access: {
|
Access: {
|
||||||
|
SaasStart: {
|
||||||
|
Title: "Usa NextChat AI",
|
||||||
|
Label: "(La soluzione più conveniente)",
|
||||||
|
SubTitle:
|
||||||
|
"Mantenuto ufficialmente da NextChat, pronto all'uso senza configurazione, supporta i modelli più recenti come OpenAI o1, GPT-4o e Claude-3.5",
|
||||||
|
ChatNow: "Chatta ora",
|
||||||
|
},
|
||||||
|
|
||||||
AccessCode: {
|
AccessCode: {
|
||||||
Title: "Password di accesso",
|
Title: "Password di accesso",
|
||||||
SubTitle: "L'amministratore ha abilitato l'accesso criptato",
|
SubTitle: "L'amministratore ha abilitato l'accesso criptato",
|
||||||
|
|
|
@ -1,15 +1,21 @@
|
||||||
import { SubmitKey } from "../store/config";
|
import { SubmitKey } from "../store/config";
|
||||||
import type { PartialLocaleType } from "./index";
|
import type { PartialLocaleType } from "./index";
|
||||||
import { getClientConfig } from "../config/client";
|
import { getClientConfig } from "../config/client";
|
||||||
|
import { SAAS_CHAT_UTM_URL } from "@/app/constant";
|
||||||
const isApp = !!getClientConfig()?.isApp;
|
const isApp = !!getClientConfig()?.isApp;
|
||||||
|
|
||||||
const jp: PartialLocaleType = {
|
const jp: PartialLocaleType = {
|
||||||
WIP: "この機能は開発中です",
|
WIP: "この機能は開発中です",
|
||||||
Error: {
|
Error: {
|
||||||
Unauthorized: isApp
|
Unauthorized: isApp
|
||||||
? "無効なAPIキーが検出されました。[設定](/#/settings)ページでAPIキーが正しく設定されているか確認してください。"
|
? `😆 会話中に問題が発生しましたが、心配しないでください:
|
||||||
: "アクセスパスワードが正しくないか空です。[ログイン](/#/auth)ページで正しいアクセスパスワードを入力するか、[設定](/#/settings)ページで自分のOpenAI APIキーを入力してください。",
|
\\ 1️⃣ 設定なしで始めたい場合は、[ここをクリックしてすぐにチャットを開始 🚀](${SAAS_CHAT_UTM_URL})
|
||||||
|
\\ 2️⃣ 自分のOpenAIリソースを使用したい場合は、[ここをクリックして](/#/settings)設定を変更してください ⚙️`
|
||||||
|
: `😆 会話中に問題が発生しましたが、心配しないでください:
|
||||||
|
\ 1️⃣ 設定なしで始めたい場合は、[ここをクリックしてすぐにチャットを開始 🚀](${SAAS_CHAT_UTM_URL})
|
||||||
|
\ 2️⃣ プライベートデプロイ版を使用している場合は、[ここをクリックして](/#/auth)アクセストークンを入力してください 🔑
|
||||||
|
\ 3️⃣ 自分のOpenAIリソースを使用したい場合は、[ここをクリックして](/#/settings)設定を変更してください ⚙️
|
||||||
|
`,
|
||||||
},
|
},
|
||||||
Auth: {
|
Auth: {
|
||||||
Title: "パスワードが必要です",
|
Title: "パスワードが必要です",
|
||||||
|
@ -18,6 +24,10 @@ const jp: PartialLocaleType = {
|
||||||
Input: "ここにアクセスコードを入力",
|
Input: "ここにアクセスコードを入力",
|
||||||
Confirm: "確認",
|
Confirm: "確認",
|
||||||
Later: "後で",
|
Later: "後で",
|
||||||
|
Return: "戻る",
|
||||||
|
SaasTips: "設定が面倒すぎる、すぐに使いたい",
|
||||||
|
TopTips:
|
||||||
|
"🥳 NextChat AIの発売特典で、OpenAI o1、GPT-4o、Claude-3.5などの最新の大規模モデルを今すぐアンロック",
|
||||||
},
|
},
|
||||||
ChatItem: {
|
ChatItem: {
|
||||||
ChatItemCount: (count: number) => `${count}件の会話`,
|
ChatItemCount: (count: number) => `${count}件の会話`,
|
||||||
|
@ -282,6 +292,14 @@ const jp: PartialLocaleType = {
|
||||||
},
|
},
|
||||||
|
|
||||||
Access: {
|
Access: {
|
||||||
|
SaasStart: {
|
||||||
|
Title: "NextChat AIを使用する",
|
||||||
|
Label: "(コストパフォーマンスの最も高いソリューション)",
|
||||||
|
SubTitle:
|
||||||
|
"NextChatによって公式に管理されており、設定なしですぐに使用でき、OpenAI o1、GPT-4o、Claude-3.5などの最新の大規模モデルをサポートしています",
|
||||||
|
ChatNow: "今すぐチャット",
|
||||||
|
},
|
||||||
|
|
||||||
AccessCode: {
|
AccessCode: {
|
||||||
Title: "アクセスパスワード",
|
Title: "アクセスパスワード",
|
||||||
SubTitle: "管理者が暗号化アクセスを有効にしました",
|
SubTitle: "管理者が暗号化アクセスを有効にしました",
|
||||||
|
|
|
@ -1,15 +1,21 @@
|
||||||
import { SubmitKey } from "../store/config";
|
import { SubmitKey } from "../store/config";
|
||||||
import type { PartialLocaleType } from "./index";
|
import type { PartialLocaleType } from "./index";
|
||||||
import { getClientConfig } from "../config/client";
|
import { getClientConfig } from "../config/client";
|
||||||
|
import { SAAS_CHAT_UTM_URL } from "@/app/constant";
|
||||||
const isApp = !!getClientConfig()?.isApp;
|
const isApp = !!getClientConfig()?.isApp;
|
||||||
|
|
||||||
const ko: PartialLocaleType = {
|
const ko: PartialLocaleType = {
|
||||||
WIP: "곧 출시 예정...",
|
WIP: "곧 출시 예정...",
|
||||||
Error: {
|
Error: {
|
||||||
Unauthorized: isApp
|
Unauthorized: isApp
|
||||||
? "유효하지 않은 API 키가 감지되었습니다. [설정](/#/settings) 페이지에서 API 키가 올바르게 구성되었는지 확인하십시오."
|
? `😆 대화 중 문제가 발생했습니다, 걱정하지 마세요:
|
||||||
: "잘못된 접근 비밀번호이거나 비밀번호가 비어 있습니다. [로그인](/#/auth) 페이지에서 올바른 접근 비밀번호를 입력하거나 [설정](/#/settings) 페이지에서 OpenAI API 키를 입력하십시오.",
|
\\ 1️⃣ 제로 구성으로 시작하고 싶다면, [여기를 클릭하여 즉시 대화를 시작하세요 🚀](${SAAS_CHAT_UTM_URL})
|
||||||
|
\\ 2️⃣ 자신의 OpenAI 리소스를 사용하고 싶다면, [여기를 클릭하여](/#/settings) 설정을 수정하세요 ⚙️`
|
||||||
|
: `😆 대화 중 문제가 발생했습니다, 걱정하지 마세요:
|
||||||
|
\ 1️⃣ 제로 구성으로 시작하고 싶다면, [여기를 클릭하여 즉시 대화를 시작하세요 🚀](${SAAS_CHAT_UTM_URL})
|
||||||
|
\ 2️⃣ 개인 배포 버전을 사용하고 있다면, [여기를 클릭하여](/#/auth) 접근 키를 입력하세요 🔑
|
||||||
|
\ 3️⃣ 자신의 OpenAI 리소스를 사용하고 싶다면, [여기를 클릭하여](/#/settings) 설정을 수정하세요 ⚙️
|
||||||
|
`,
|
||||||
},
|
},
|
||||||
Auth: {
|
Auth: {
|
||||||
Title: "비밀번호 필요",
|
Title: "비밀번호 필요",
|
||||||
|
@ -18,6 +24,10 @@ const ko: PartialLocaleType = {
|
||||||
Input: "여기에 접근 코드를 입력하십시오.",
|
Input: "여기에 접근 코드를 입력하십시오.",
|
||||||
Confirm: "확인",
|
Confirm: "확인",
|
||||||
Later: "나중에 하기",
|
Later: "나중에 하기",
|
||||||
|
Return: "돌아가기",
|
||||||
|
SaasTips: "설정이 너무 복잡합니다. 즉시 사용하고 싶습니다.",
|
||||||
|
TopTips:
|
||||||
|
"🥳 NextChat AI 출시 기념 할인, 지금 OpenAI o1, GPT-4o, Claude-3.5 및 최신 대형 모델을 해제하세요",
|
||||||
},
|
},
|
||||||
ChatItem: {
|
ChatItem: {
|
||||||
ChatItemCount: (count: number) => `${count} 개의 대화`,
|
ChatItemCount: (count: number) => `${count} 개의 대화`,
|
||||||
|
@ -281,6 +291,14 @@ const ko: PartialLocaleType = {
|
||||||
},
|
},
|
||||||
|
|
||||||
Access: {
|
Access: {
|
||||||
|
SaasStart: {
|
||||||
|
Title: "NextChat AI 사용하기",
|
||||||
|
Label: "(가장 비용 효율적인 솔루션)",
|
||||||
|
SubTitle:
|
||||||
|
"NextChat에 의해 공식적으로 유지 관리되며, 제로 구성으로 즉시 사용할 수 있으며, OpenAI o1, GPT-4o, Claude-3.5와 같은 최신 대형 모델을 지원합니다",
|
||||||
|
ChatNow: "지금 채팅하기",
|
||||||
|
},
|
||||||
|
|
||||||
AccessCode: {
|
AccessCode: {
|
||||||
Title: "접근 비밀번호",
|
Title: "접근 비밀번호",
|
||||||
SubTitle: "관리자가 암호화된 접근을 활성화했습니다.",
|
SubTitle: "관리자가 암호화된 접근을 활성화했습니다.",
|
||||||
|
|
|
@ -1,15 +1,21 @@
|
||||||
import { SubmitKey } from "../store/config";
|
import { SubmitKey } from "../store/config";
|
||||||
import type { PartialLocaleType } from "./index";
|
import type { PartialLocaleType } from "./index";
|
||||||
import { getClientConfig } from "../config/client";
|
import { getClientConfig } from "../config/client";
|
||||||
|
import { SAAS_CHAT_UTM_URL } from "@/app/constant";
|
||||||
const isApp = !!getClientConfig()?.isApp;
|
const isApp = !!getClientConfig()?.isApp;
|
||||||
|
|
||||||
const no: PartialLocaleType = {
|
const no: PartialLocaleType = {
|
||||||
WIP: "Arbeid pågår ...",
|
WIP: "Arbeid pågår ...",
|
||||||
Error: {
|
Error: {
|
||||||
Unauthorized: isApp
|
Unauthorized: isApp
|
||||||
? "Ugyldig API-nøkkel oppdaget. Gå til [innstillinger](/#/settings) for å sjekke om API-nøkkelen er riktig konfigurert."
|
? `😆 Samtalen har støtt på noen problemer, ikke bekymre deg:
|
||||||
: "Adgangskoden er feil eller tom. Gå til [innlogging](/#/auth) for å oppgi riktig adgangskode, eller fyll inn din egen OpenAI API-nøkkel på [innstillinger](/#/settings) siden.",
|
\\ 1️⃣ Hvis du vil starte uten konfigurasjon, [klikk her for å begynne å chatte umiddelbart 🚀](${SAAS_CHAT_UTM_URL})
|
||||||
|
\\ 2️⃣ Hvis du vil bruke dine egne OpenAI-ressurser, klikk [her](/#/settings) for å endre innstillingene ⚙️`
|
||||||
|
: `😆 Samtalen har støtt på noen problemer, ikke bekymre deg:
|
||||||
|
\ 1️⃣ Hvis du vil starte uten konfigurasjon, [klikk her for å begynne å chatte umiddelbart 🚀](${SAAS_CHAT_UTM_URL})
|
||||||
|
\ 2️⃣ Hvis du bruker en privat distribusjonsversjon, klikk [her](/#/auth) for å skrive inn tilgangsnøkkelen 🔑
|
||||||
|
\ 3️⃣ Hvis du vil bruke dine egne OpenAI-ressurser, klikk [her](/#/settings) for å endre innstillingene ⚙️
|
||||||
|
`,
|
||||||
},
|
},
|
||||||
Auth: {
|
Auth: {
|
||||||
Title: "Passord påkrevd",
|
Title: "Passord påkrevd",
|
||||||
|
@ -18,6 +24,11 @@ const no: PartialLocaleType = {
|
||||||
Input: "Skriv tilgangskoden her",
|
Input: "Skriv tilgangskoden her",
|
||||||
Confirm: "Bekreft",
|
Confirm: "Bekreft",
|
||||||
Later: "Kom tilbake senere",
|
Later: "Kom tilbake senere",
|
||||||
|
Return: "Tilbake",
|
||||||
|
SaasTips:
|
||||||
|
"Konfigurasjonen er for komplisert, jeg vil bruke det med en gang",
|
||||||
|
TopTips:
|
||||||
|
"🥳 NextChat AI lanseringstilbud, lås opp OpenAI o1, GPT-4o, Claude-3.5 og de nyeste store modellene nå",
|
||||||
},
|
},
|
||||||
ChatItem: {
|
ChatItem: {
|
||||||
ChatItemCount: (count: number) => `${count} samtaler`,
|
ChatItemCount: (count: number) => `${count} samtaler`,
|
||||||
|
@ -288,6 +299,14 @@ const no: PartialLocaleType = {
|
||||||
},
|
},
|
||||||
|
|
||||||
Access: {
|
Access: {
|
||||||
|
SaasStart: {
|
||||||
|
Title: "Bruk NextChat AI",
|
||||||
|
Label: "(Den mest kostnadseffektive løsningen)",
|
||||||
|
SubTitle:
|
||||||
|
"Offisielt vedlikeholdt av NextChat, klar til bruk uten konfigurasjon, støtter de nyeste store modellene som OpenAI o1, GPT-4o og Claude-3.5",
|
||||||
|
ChatNow: "Chat nå",
|
||||||
|
},
|
||||||
|
|
||||||
AccessCode: {
|
AccessCode: {
|
||||||
Title: "Adgangskode",
|
Title: "Adgangskode",
|
||||||
SubTitle: "Administrator har aktivert kryptert tilgang",
|
SubTitle: "Administrator har aktivert kryptert tilgang",
|
||||||
|
|
|
@ -1,15 +1,21 @@
|
||||||
import { SubmitKey } from "../store/config";
|
import { SubmitKey } from "../store/config";
|
||||||
import { PartialLocaleType } from "../locales/index";
|
import { PartialLocaleType } from "../locales/index";
|
||||||
import { getClientConfig } from "../config/client";
|
import { getClientConfig } from "../config/client";
|
||||||
|
import { SAAS_CHAT_UTM_URL } from "@/app/constant";
|
||||||
const isApp = !!getClientConfig()?.isApp;
|
const isApp = !!getClientConfig()?.isApp;
|
||||||
|
|
||||||
const pt: PartialLocaleType = {
|
const pt: PartialLocaleType = {
|
||||||
WIP: "Em breve...",
|
WIP: "Em breve...",
|
||||||
Error: {
|
Error: {
|
||||||
Unauthorized: isApp
|
Unauthorized: isApp
|
||||||
? "Chave API inválida, por favor verifique em [Configurações](/#/settings)."
|
? `😆 A conversa encontrou alguns problemas, não se preocupe:
|
||||||
: "Acesso não autorizado, por favor insira o código de acesso em [auth](/#/auth) ou insira sua Chave API OpenAI.",
|
\\ 1️⃣ Se você quiser começar sem configuração, [clique aqui para começar a conversar imediatamente 🚀](${SAAS_CHAT_UTM_URL})
|
||||||
|
\\ 2️⃣ Se você deseja usar seus próprios recursos OpenAI, clique [aqui](/#/settings) para modificar as configurações ⚙️`
|
||||||
|
: `😆 A conversa encontrou alguns problemas, não se preocupe:
|
||||||
|
\ 1️⃣ Se você quiser começar sem configuração, [clique aqui para começar a conversar imediatamente 🚀](${SAAS_CHAT_UTM_URL})
|
||||||
|
\ 2️⃣ Se você estiver usando uma versão de implantação privada, clique [aqui](/#/auth) para inserir a chave de acesso 🔑
|
||||||
|
\ 3️⃣ Se você deseja usar seus próprios recursos OpenAI, clique [aqui](/#/settings) para modificar as configurações ⚙️
|
||||||
|
`,
|
||||||
},
|
},
|
||||||
Auth: {
|
Auth: {
|
||||||
Title: "Necessário Código de Acesso",
|
Title: "Necessário Código de Acesso",
|
||||||
|
@ -18,6 +24,10 @@ const pt: PartialLocaleType = {
|
||||||
Input: "código de acesso",
|
Input: "código de acesso",
|
||||||
Confirm: "Confirmar",
|
Confirm: "Confirmar",
|
||||||
Later: "Depois",
|
Later: "Depois",
|
||||||
|
Return: "Voltar",
|
||||||
|
SaasTips: "A configuração é muito complicada, quero usá-la imediatamente",
|
||||||
|
TopTips:
|
||||||
|
"🥳 Oferta de Lançamento do NextChat AI, desbloqueie o OpenAI o1, GPT-4o, Claude-3.5 e os mais recentes grandes modelos agora",
|
||||||
},
|
},
|
||||||
ChatItem: {
|
ChatItem: {
|
||||||
ChatItemCount: (count: number) => `${count} mensagens`,
|
ChatItemCount: (count: number) => `${count} mensagens`,
|
||||||
|
@ -281,6 +291,14 @@ const pt: PartialLocaleType = {
|
||||||
NoAccess: "Insira a Chave API para verificar o saldo",
|
NoAccess: "Insira a Chave API para verificar o saldo",
|
||||||
},
|
},
|
||||||
Access: {
|
Access: {
|
||||||
|
SaasStart: {
|
||||||
|
Title: "Usar NextChat AI",
|
||||||
|
Label: "(A solução mais econômica)",
|
||||||
|
SubTitle:
|
||||||
|
"Mantido oficialmente pelo NextChat, pronto para uso sem configuração, suporta os mais recentes grandes modelos como OpenAI o1, GPT-4o e Claude-3.5",
|
||||||
|
ChatNow: "Conversar agora",
|
||||||
|
},
|
||||||
|
|
||||||
AccessCode: {
|
AccessCode: {
|
||||||
Title: "Código de Acesso",
|
Title: "Código de Acesso",
|
||||||
SubTitle: "Controle de Acesso Habilitado",
|
SubTitle: "Controle de Acesso Habilitado",
|
||||||
|
|
|
@ -1,15 +1,21 @@
|
||||||
import { SubmitKey } from "../store/config";
|
import { SubmitKey } from "../store/config";
|
||||||
import { PartialLocaleType } from "../locales/index";
|
import { PartialLocaleType } from "../locales/index";
|
||||||
import { getClientConfig } from "../config/client";
|
import { getClientConfig } from "../config/client";
|
||||||
|
import { SAAS_CHAT_UTM_URL } from "@/app/constant";
|
||||||
const isApp = !!getClientConfig()?.isApp;
|
const isApp = !!getClientConfig()?.isApp;
|
||||||
|
|
||||||
const ru: PartialLocaleType = {
|
const ru: PartialLocaleType = {
|
||||||
WIP: "Скоро...",
|
WIP: "Скоро...",
|
||||||
Error: {
|
Error: {
|
||||||
Unauthorized: isApp
|
Unauthorized: isApp
|
||||||
? "Обнаружен недействительный API-ключ. Пожалуйста, перейдите на страницу [Настройки](/#/settings), чтобы проверить правильность конфигурации API-ключа."
|
? `😆 В разговоре возникли некоторые проблемы, не переживайте:
|
||||||
: "Неверный или пустой пароль доступа. Пожалуйста, перейдите на страницу [Вход](/#/auth), чтобы ввести правильный пароль доступа, или на страницу [Настройки](/#/settings), чтобы ввести ваш собственный API-ключ OpenAI.",
|
\\ 1️⃣ Если вы хотите начать без настройки, [нажмите здесь, чтобы немедленно начать разговор 🚀](${SAAS_CHAT_UTM_URL})
|
||||||
|
\\ 2️⃣ Если вы хотите использовать свои ресурсы OpenAI, нажмите [здесь](/#/settings), чтобы изменить настройки ⚙️`
|
||||||
|
: `😆 В разговоре возникли некоторые проблемы, не переживайте:
|
||||||
|
\ 1️⃣ Если вы хотите начать без настройки, [нажмите здесь, чтобы немедленно начать разговор 🚀](${SAAS_CHAT_UTM_URL})
|
||||||
|
\ 2️⃣ Если вы используете частную версию развертывания, нажмите [здесь](/#/auth), чтобы ввести ключ доступа 🔑
|
||||||
|
\ 3️⃣ Если вы хотите использовать свои ресурсы OpenAI, нажмите [здесь](/#/settings), чтобы изменить настройки ⚙️
|
||||||
|
`,
|
||||||
},
|
},
|
||||||
Auth: {
|
Auth: {
|
||||||
Title: "Требуется пароль",
|
Title: "Требуется пароль",
|
||||||
|
@ -18,6 +24,10 @@ const ru: PartialLocaleType = {
|
||||||
Input: "Введите код доступа здесь",
|
Input: "Введите код доступа здесь",
|
||||||
Confirm: "Подтвердить",
|
Confirm: "Подтвердить",
|
||||||
Later: "Позже",
|
Later: "Позже",
|
||||||
|
Return: "Назад",
|
||||||
|
SaasTips: "Настройка слишком сложна, я хочу использовать это немедленно",
|
||||||
|
TopTips:
|
||||||
|
"🥳 Предложение по запуску NextChat AI: разблокируйте OpenAI o1, GPT-4o, Claude-3.5 и новейшие большие модели прямо сейчас",
|
||||||
},
|
},
|
||||||
ChatItem: {
|
ChatItem: {
|
||||||
ChatItemCount: (count: number) => `${count} бесед`,
|
ChatItemCount: (count: number) => `${count} бесед`,
|
||||||
|
@ -286,6 +296,14 @@ const ru: PartialLocaleType = {
|
||||||
},
|
},
|
||||||
|
|
||||||
Access: {
|
Access: {
|
||||||
|
SaasStart: {
|
||||||
|
Title: "Используйте NextChat AI",
|
||||||
|
Label: "(Самое экономичное решение)",
|
||||||
|
SubTitle:
|
||||||
|
"Официально поддерживается NextChat, готов к использованию без настройки, поддерживает последние крупные модели, такие как OpenAI o1, GPT-4o и Claude-3.5",
|
||||||
|
ChatNow: "Начать чат",
|
||||||
|
},
|
||||||
|
|
||||||
AccessCode: {
|
AccessCode: {
|
||||||
Title: "Пароль доступа",
|
Title: "Пароль доступа",
|
||||||
SubTitle: "Администратор включил защиту паролем",
|
SubTitle: "Администратор включил защиту паролем",
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import { getClientConfig } from "../config/client";
|
import { getClientConfig } from "../config/client";
|
||||||
import { SubmitKey } from "../store/config";
|
import { SubmitKey } from "../store/config";
|
||||||
import { LocaleType } from "./index";
|
|
||||||
import type { PartialLocaleType } from "./index";
|
import type { PartialLocaleType } from "./index";
|
||||||
|
import { SAAS_CHAT_UTM_URL } from "@/app/constant";
|
||||||
// if you are adding a new translation, please use PartialLocaleType instead of LocaleType
|
// if you are adding a new translation, please use PartialLocaleType instead of LocaleType
|
||||||
|
|
||||||
const isApp = !!getClientConfig()?.isApp;
|
const isApp = !!getClientConfig()?.isApp;
|
||||||
|
@ -10,8 +9,14 @@ const sk: PartialLocaleType = {
|
||||||
WIP: "Už čoskoro...",
|
WIP: "Už čoskoro...",
|
||||||
Error: {
|
Error: {
|
||||||
Unauthorized: isApp
|
Unauthorized: isApp
|
||||||
? "Neplatný API kľúč, prosím skontrolujte ho na stránke [Nastavenia](/#/settings)."
|
? `😆 Rozhovor narazil na nejaké problémy, nebojte sa:
|
||||||
: "Neoprávnený prístup, prosím zadajte prístupový kód na stránke [auth](/#/auth), alebo zadajte váš OpenAI API kľúč.",
|
\\ 1️⃣ Ak chcete začať bez konfigurácie, [kliknite sem, aby ste okamžite začali chatovať 🚀](${SAAS_CHAT_UTM_URL})
|
||||||
|
\\ 2️⃣ Ak chcete používať svoje vlastné zdroje OpenAI, kliknite [sem](/#/settings), aby ste upravili nastavenia ⚙️`
|
||||||
|
: `😆 Rozhovor narazil na nejaké problémy, nebojte sa:
|
||||||
|
\ 1️⃣ Ak chcete začať bez konfigurácie, [kliknite sem, aby ste okamžite začali chatovať 🚀](${SAAS_CHAT_UTM_URL})
|
||||||
|
\ 2️⃣ Ak používate verziu súkromného nasadenia, kliknite [sem](/#/auth), aby ste zadali prístupový kľúč 🔑
|
||||||
|
\ 3️⃣ Ak chcete používať svoje vlastné zdroje OpenAI, kliknite [sem](/#/settings), aby ste upravili nastavenia ⚙️
|
||||||
|
`,
|
||||||
},
|
},
|
||||||
Auth: {
|
Auth: {
|
||||||
Title: "Potrebný prístupový kód",
|
Title: "Potrebný prístupový kód",
|
||||||
|
@ -20,6 +25,10 @@ const sk: PartialLocaleType = {
|
||||||
Input: "prístupový kód",
|
Input: "prístupový kód",
|
||||||
Confirm: "Potvrdiť",
|
Confirm: "Potvrdiť",
|
||||||
Later: "Neskôr",
|
Later: "Neskôr",
|
||||||
|
Return: "Návrat",
|
||||||
|
SaasTips: "Nastavenie je príliš zložité, chcem to okamžite použiť",
|
||||||
|
TopTips:
|
||||||
|
"🥳 Uvítacia ponuka NextChat AI, okamžite odomknite OpenAI o1, GPT-4o, Claude-3.5 a najnovšie veľké modely",
|
||||||
},
|
},
|
||||||
ChatItem: {
|
ChatItem: {
|
||||||
ChatItemCount: (count: number) => `${count} správ`,
|
ChatItemCount: (count: number) => `${count} správ`,
|
||||||
|
@ -282,6 +291,14 @@ const sk: PartialLocaleType = {
|
||||||
NoAccess: "Zadajte API kľúč na skontrolovanie zostatku",
|
NoAccess: "Zadajte API kľúč na skontrolovanie zostatku",
|
||||||
},
|
},
|
||||||
Access: {
|
Access: {
|
||||||
|
SaasStart: {
|
||||||
|
Title: "Použite NextChat AI",
|
||||||
|
Label: "(Najvýhodnejšie riešenie)",
|
||||||
|
SubTitle:
|
||||||
|
"Oficiálne udržiavané NextChat, pripravené na použitie bez konfigurácie, podporuje najnovšie veľké modely ako OpenAI o1, GPT-4o a Claude-3.5",
|
||||||
|
ChatNow: "Chatovať teraz",
|
||||||
|
},
|
||||||
|
|
||||||
AccessCode: {
|
AccessCode: {
|
||||||
Title: "Prístupový kód",
|
Title: "Prístupový kód",
|
||||||
SubTitle: "Povolený prístupový kód",
|
SubTitle: "Povolený prístupový kód",
|
||||||
|
|
|
@ -1,15 +1,21 @@
|
||||||
import { SubmitKey } from "../store/config";
|
import { SubmitKey } from "../store/config";
|
||||||
import type { PartialLocaleType } from "./index";
|
import type { PartialLocaleType } from "./index";
|
||||||
import { getClientConfig } from "../config/client";
|
import { getClientConfig } from "../config/client";
|
||||||
|
import { SAAS_CHAT_UTM_URL } from "@/app/constant";
|
||||||
const isApp = !!getClientConfig()?.isApp;
|
const isApp = !!getClientConfig()?.isApp;
|
||||||
|
|
||||||
const tr: PartialLocaleType = {
|
const tr: PartialLocaleType = {
|
||||||
WIP: "Çalışma devam ediyor...",
|
WIP: "Çalışma devam ediyor...",
|
||||||
Error: {
|
Error: {
|
||||||
Unauthorized: isApp
|
Unauthorized: isApp
|
||||||
? "Geçersiz API Anahtarı tespit edildi, lütfen API Anahtarını doğru şekilde yapılandırmak için [Ayarlar](/#/settings) sayfasına gidin."
|
? `😆 Sohbet bazı sorunlarla karşılaştı, endişelenmeyin:
|
||||||
: "Erişim şifresi yanlış veya boş, lütfen doğru erişim şifresini girmek için [Giriş](/#/auth) sayfasına gidin veya kendi OpenAI API Anahtarınızı [Ayarlar](/#/settings) sayfasına girin.",
|
\\ 1️⃣ Eğer sıfır yapılandırma ile başlamak istiyorsanız, [buraya tıklayarak hemen sohbete başlayın 🚀](${SAAS_CHAT_UTM_URL})
|
||||||
|
\\ 2️⃣ Kendi OpenAI kaynaklarınızı kullanmak istiyorsanız, [buraya tıklayarak](/#/settings) ayarları değiştirin ⚙️`
|
||||||
|
: `😆 Sohbet bazı sorunlarla karşılaştı, endişelenmeyin:
|
||||||
|
\ 1️⃣ Eğer sıfır yapılandırma ile başlamak istiyorsanız, [buraya tıklayarak hemen sohbete başlayın 🚀](${SAAS_CHAT_UTM_URL})
|
||||||
|
\ 2️⃣ Eğer özel dağıtım sürümü kullanıyorsanız, [buraya tıklayarak](/#/auth) erişim anahtarını girin 🔑
|
||||||
|
\ 3️⃣ Kendi OpenAI kaynaklarınızı kullanmak istiyorsanız, [buraya tıklayarak](/#/settings) ayarları değiştirin ⚙️
|
||||||
|
`,
|
||||||
},
|
},
|
||||||
Auth: {
|
Auth: {
|
||||||
Title: "Şifre Gerekli",
|
Title: "Şifre Gerekli",
|
||||||
|
@ -18,6 +24,10 @@ const tr: PartialLocaleType = {
|
||||||
Input: "Erişim kodunu buraya girin",
|
Input: "Erişim kodunu buraya girin",
|
||||||
Confirm: "Onayla",
|
Confirm: "Onayla",
|
||||||
Later: "Sonra",
|
Later: "Sonra",
|
||||||
|
Return: "Geri",
|
||||||
|
SaasTips: "Ayarlar çok karmaşık, hemen kullanmak istiyorum",
|
||||||
|
TopTips:
|
||||||
|
"🥳 NextChat AI lansman teklifi, OpenAI o1, GPT-4o, Claude-3.5 ve en son büyük modelleri şimdi açın",
|
||||||
},
|
},
|
||||||
ChatItem: {
|
ChatItem: {
|
||||||
ChatItemCount: (count: number) => `${count} konuşma`,
|
ChatItemCount: (count: number) => `${count} konuşma`,
|
||||||
|
@ -286,6 +296,14 @@ const tr: PartialLocaleType = {
|
||||||
},
|
},
|
||||||
|
|
||||||
Access: {
|
Access: {
|
||||||
|
SaasStart: {
|
||||||
|
Title: "NextChat AI kullanın",
|
||||||
|
Label: "(En maliyet etkin çözüm)",
|
||||||
|
SubTitle:
|
||||||
|
"NextChat tarafından resmi olarak yönetilmektedir, yapılandırma olmadan hemen kullanıma hazırdır, OpenAI o1, GPT-4o, Claude-3.5 gibi en son büyük modelleri destekler",
|
||||||
|
ChatNow: "Şimdi sohbet et",
|
||||||
|
},
|
||||||
|
|
||||||
AccessCode: {
|
AccessCode: {
|
||||||
Title: "Erişim Şifresi",
|
Title: "Erişim Şifresi",
|
||||||
SubTitle: "Yönetici şifreli erişimi etkinleştirdi",
|
SubTitle: "Yönetici şifreli erişimi etkinleştirdi",
|
||||||
|
|
|
@ -1,14 +1,20 @@
|
||||||
import { getClientConfig } from "../config/client";
|
import { getClientConfig } from "../config/client";
|
||||||
import { SubmitKey } from "../store/config";
|
import { SubmitKey } from "../store/config";
|
||||||
|
import { SAAS_CHAT_UTM_URL } from "@/app/constant";
|
||||||
const isApp = !!getClientConfig()?.isApp;
|
const isApp = !!getClientConfig()?.isApp;
|
||||||
|
|
||||||
const tw = {
|
const tw = {
|
||||||
WIP: "此功能仍在開發中……",
|
WIP: "此功能仍在開發中……",
|
||||||
Error: {
|
Error: {
|
||||||
Unauthorized: isApp
|
Unauthorized: isApp
|
||||||
? "偵測到無效的 API Key,請前往[設定](/#/settings)頁面檢查 API Key 是否設定正確。"
|
? `😆 對話遇到了一些問題,不用慌:
|
||||||
: "存取密碼不正確或尚未填寫,請前往[登入](/#/auth)頁面輸入正確的存取密碼,或者在[設定](/#/settings)頁面填入你自己的 OpenAI API Key。",
|
\\ 1️⃣ 想要零配置開箱即用,[點擊這裡立刻開啟對話 🚀](${SAAS_CHAT_UTM_URL})
|
||||||
|
\\ 2️⃣ 如果你想消耗自己的 OpenAI 資源,點擊[這裡](/#/settings)修改設定 ⚙️`
|
||||||
|
: `😆 對話遇到了一些問題,不用慌:
|
||||||
|
\ 1️⃣ 想要零配置開箱即用,[點擊這裡立刻開啟對話 🚀](${SAAS_CHAT_UTM_URL})
|
||||||
|
\ 2️⃣ 如果你正在使用私有部署版本,點擊[這裡](/#/auth)輸入訪問秘鑰 🔑
|
||||||
|
\ 3️⃣ 如果你想消耗自己的 OpenAI 資源,點擊[這裡](/#/settings)修改設定 ⚙️
|
||||||
|
`,
|
||||||
},
|
},
|
||||||
|
|
||||||
Auth: {
|
Auth: {
|
||||||
|
@ -18,6 +24,10 @@ const tw = {
|
||||||
Input: "在此處填寫存取密碼",
|
Input: "在此處填寫存取密碼",
|
||||||
Confirm: "確認",
|
Confirm: "確認",
|
||||||
Later: "稍候再說",
|
Later: "稍候再說",
|
||||||
|
Return: "返回",
|
||||||
|
SaasTips: "配置太麻煩,想要立即使用",
|
||||||
|
TopTips:
|
||||||
|
"🥳 NextChat AI 首發優惠,立刻解鎖 OpenAI o1, GPT-4o, Claude-3.5 等最新大模型",
|
||||||
},
|
},
|
||||||
ChatItem: {
|
ChatItem: {
|
||||||
ChatItemCount: (count: number) => `${count} 則對話`,
|
ChatItemCount: (count: number) => `${count} 則對話`,
|
||||||
|
@ -287,6 +297,14 @@ const tw = {
|
||||||
},
|
},
|
||||||
|
|
||||||
Access: {
|
Access: {
|
||||||
|
SaasStart: {
|
||||||
|
Title: "使用 NextChat AI",
|
||||||
|
Label: "(性價比最高的方案)",
|
||||||
|
SubTitle:
|
||||||
|
"由 NextChat 官方維護,零配置開箱即用,支持 OpenAI o1、GPT-4o、Claude-3.5 等最新大模型",
|
||||||
|
ChatNow: "立刻對話",
|
||||||
|
},
|
||||||
|
|
||||||
AccessCode: {
|
AccessCode: {
|
||||||
Title: "存取密碼",
|
Title: "存取密碼",
|
||||||
SubTitle: "管理員已開啟加密存取",
|
SubTitle: "管理員已開啟加密存取",
|
||||||
|
|
|
@ -1,15 +1,21 @@
|
||||||
import { SubmitKey } from "../store/config";
|
import { SubmitKey } from "../store/config";
|
||||||
import type { PartialLocaleType } from "./index";
|
import type { PartialLocaleType } from "./index";
|
||||||
import { getClientConfig } from "../config/client";
|
import { getClientConfig } from "../config/client";
|
||||||
|
import { SAAS_CHAT_UTM_URL } from "@/app/constant";
|
||||||
const isApp = !!getClientConfig()?.isApp;
|
const isApp = !!getClientConfig()?.isApp;
|
||||||
|
|
||||||
const vi: PartialLocaleType = {
|
const vi: PartialLocaleType = {
|
||||||
WIP: "Sắp ra mắt...",
|
WIP: "Sắp ra mắt...",
|
||||||
Error: {
|
Error: {
|
||||||
Unauthorized: isApp
|
Unauthorized: isApp
|
||||||
? "Phát hiện khóa API không hợp lệ, vui lòng truy cập trang [Cài đặt](/#/settings) để kiểm tra xem khóa API có được cấu hình chính xác không."
|
? `😆 Cuộc trò chuyện gặp một số vấn đề, đừng lo lắng:
|
||||||
: "Mật khẩu truy cập không đúng hoặc để trống, vui lòng truy cập trang [Đăng nhập](/#/auth) để nhập mật khẩu truy cập chính xác, hoặc điền khóa API OpenAI của bạn vào trang [Cài đặt](/#/settings).",
|
\\ 1️⃣ Nếu bạn muốn bắt đầu mà không cần cấu hình, [nhấp vào đây để bắt đầu trò chuyện ngay lập tức 🚀](${SAAS_CHAT_UTM_URL})
|
||||||
|
\\ 2️⃣ Nếu bạn muốn sử dụng tài nguyên OpenAI của riêng mình, hãy nhấp [vào đây](/#/settings) để thay đổi cài đặt ⚙️`
|
||||||
|
: `😆 Cuộc trò chuyện gặp một số vấn đề, đừng lo lắng:
|
||||||
|
\ 1️⃣ Nếu bạn muốn bắt đầu mà không cần cấu hình, [nhấp vào đây để bắt đầu trò chuyện ngay lập tức 🚀](${SAAS_CHAT_UTM_URL})
|
||||||
|
\ 2️⃣ Nếu bạn đang sử dụng phiên bản triển khai riêng, hãy nhấp [vào đây](/#/auth) để nhập khóa truy cập 🔑
|
||||||
|
\ 3️⃣ Nếu bạn muốn sử dụng tài nguyên OpenAI của riêng mình, hãy nhấp [vào đây](/#/settings) để thay đổi cài đặt ⚙️
|
||||||
|
`,
|
||||||
},
|
},
|
||||||
Auth: {
|
Auth: {
|
||||||
Title: "Cần mật khẩu",
|
Title: "Cần mật khẩu",
|
||||||
|
@ -18,6 +24,10 @@ const vi: PartialLocaleType = {
|
||||||
Input: "Nhập mã truy cập tại đây",
|
Input: "Nhập mã truy cập tại đây",
|
||||||
Confirm: "Xác nhận",
|
Confirm: "Xác nhận",
|
||||||
Later: "Để sau",
|
Later: "Để sau",
|
||||||
|
Return: "Trở lại",
|
||||||
|
SaasTips: "Cấu hình quá phức tạp, tôi muốn sử dụng ngay lập tức",
|
||||||
|
TopTips:
|
||||||
|
"🥳 Ưu đãi ra mắt NextChat AI, mở khóa OpenAI o1, GPT-4o, Claude-3.5 và các mô hình lớn mới nhất ngay bây giờ",
|
||||||
},
|
},
|
||||||
ChatItem: {
|
ChatItem: {
|
||||||
ChatItemCount: (count: number) => `${count} cuộc trò chuyện`,
|
ChatItemCount: (count: number) => `${count} cuộc trò chuyện`,
|
||||||
|
@ -283,6 +293,14 @@ const vi: PartialLocaleType = {
|
||||||
},
|
},
|
||||||
|
|
||||||
Access: {
|
Access: {
|
||||||
|
SaasStart: {
|
||||||
|
Title: "Sử dụng NextChat AI",
|
||||||
|
Label: "(Giải pháp tiết kiệm chi phí nhất)",
|
||||||
|
SubTitle:
|
||||||
|
"Được NextChat chính thức duy trì, sẵn sàng sử dụng mà không cần cấu hình, hỗ trợ các mô hình lớn mới nhất như OpenAI o1, GPT-4o và Claude-3.5",
|
||||||
|
ChatNow: "Chat ngay",
|
||||||
|
},
|
||||||
|
|
||||||
AccessCode: {
|
AccessCode: {
|
||||||
Title: "Mật khẩu truy cập",
|
Title: "Mật khẩu truy cập",
|
||||||
SubTitle: "Quản trị viên đã bật truy cập mã hóa",
|
SubTitle: "Quản trị viên đã bật truy cập mã hóa",
|
||||||
|
|
|
@ -1,7 +1,4 @@
|
||||||
import { Mask } from "../store/mask";
|
import { Mask } from "../store/mask";
|
||||||
import { CN_MASKS } from "./cn";
|
|
||||||
import { TW_MASKS } from "./tw";
|
|
||||||
import { EN_MASKS } from "./en";
|
|
||||||
|
|
||||||
import { type BuiltinMask } from "./typing";
|
import { type BuiltinMask } from "./typing";
|
||||||
export { type BuiltinMask } from "./typing";
|
export { type BuiltinMask } from "./typing";
|
||||||
|
|
|
@ -120,6 +120,9 @@ const DEFAULT_ACCESS_STATE = {
|
||||||
disableFastLink: false,
|
disableFastLink: false,
|
||||||
customModels: "",
|
customModels: "",
|
||||||
defaultModel: "",
|
defaultModel: "",
|
||||||
|
|
||||||
|
// tts config
|
||||||
|
edgeTTSVoiceName: "zh-CN-YunxiNeural",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useAccessStore = createPersistStore(
|
export const useAccessStore = createPersistStore(
|
||||||
|
@ -132,6 +135,12 @@ export const useAccessStore = createPersistStore(
|
||||||
return get().needCode;
|
return get().needCode;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
edgeVoiceName() {
|
||||||
|
this.fetch();
|
||||||
|
|
||||||
|
return get().edgeTTSVoiceName;
|
||||||
|
},
|
||||||
|
|
||||||
isValidOpenAI() {
|
isValidOpenAI() {
|
||||||
return ensure(get(), ["openaiApiKey"]);
|
return ensure(get(), ["openaiApiKey"]);
|
||||||
},
|
},
|
||||||
|
@ -202,10 +211,13 @@ export const useAccessStore = createPersistStore(
|
||||||
})
|
})
|
||||||
.then((res) => res.json())
|
.then((res) => res.json())
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
// Set default model from env request
|
const defaultModel = res.defaultModel ?? "";
|
||||||
let defaultModel = res.defaultModel ?? "";
|
if (defaultModel !== "") {
|
||||||
DEFAULT_CONFIG.modelConfig.model =
|
const [model, providerName] = defaultModel.split("@");
|
||||||
defaultModel !== "" ? defaultModel : "gpt-3.5-turbo";
|
DEFAULT_CONFIG.modelConfig.model = model;
|
||||||
|
DEFAULT_CONFIG.modelConfig.providerName = providerName;
|
||||||
|
}
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
})
|
})
|
||||||
.then((res: DangerConfig) => {
|
.then((res: DangerConfig) => {
|
||||||
|
|
|
@ -170,6 +170,28 @@ export const useChatStore = createPersistStore(
|
||||||
}
|
}
|
||||||
|
|
||||||
const methods = {
|
const methods = {
|
||||||
|
forkSession() {
|
||||||
|
// 获取当前会话
|
||||||
|
const currentSession = get().currentSession();
|
||||||
|
if (!currentSession) return;
|
||||||
|
|
||||||
|
const newSession = createEmptySession();
|
||||||
|
|
||||||
|
newSession.topic = currentSession.topic;
|
||||||
|
newSession.messages = [...currentSession.messages];
|
||||||
|
newSession.mask = {
|
||||||
|
...currentSession.mask,
|
||||||
|
modelConfig: {
|
||||||
|
...currentSession.mask.modelConfig,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
set((state) => ({
|
||||||
|
currentSessionIndex: 0,
|
||||||
|
sessions: [newSession, ...state.sessions],
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
clearSessions() {
|
clearSessions() {
|
||||||
set(() => ({
|
set(() => ({
|
||||||
sessions: [createEmptySession()],
|
sessions: [createEmptySession()],
|
||||||
|
@ -593,6 +615,7 @@ export const useChatStore = createPersistStore(
|
||||||
providerName,
|
providerName,
|
||||||
},
|
},
|
||||||
onFinish(message) {
|
onFinish(message) {
|
||||||
|
if (!isValidMessage(message)) return;
|
||||||
get().updateCurrentSession(
|
get().updateCurrentSession(
|
||||||
(session) =>
|
(session) =>
|
||||||
(session.topic =
|
(session.topic =
|
||||||
|
@ -668,6 +691,10 @@ export const useChatStore = createPersistStore(
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isValidMessage(message: any): boolean {
|
||||||
|
return typeof message === "string" && !message.startsWith("```json");
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
updateStat(message: ChatMessage) {
|
updateStat(message: ChatMessage) {
|
||||||
|
|
|
@ -5,12 +5,21 @@ import {
|
||||||
DEFAULT_INPUT_TEMPLATE,
|
DEFAULT_INPUT_TEMPLATE,
|
||||||
DEFAULT_MODELS,
|
DEFAULT_MODELS,
|
||||||
DEFAULT_SIDEBAR_WIDTH,
|
DEFAULT_SIDEBAR_WIDTH,
|
||||||
|
DEFAULT_TTS_ENGINE,
|
||||||
|
DEFAULT_TTS_ENGINES,
|
||||||
|
DEFAULT_TTS_MODEL,
|
||||||
|
DEFAULT_TTS_MODELS,
|
||||||
|
DEFAULT_TTS_VOICE,
|
||||||
|
DEFAULT_TTS_VOICES,
|
||||||
StoreKey,
|
StoreKey,
|
||||||
ServiceProvider,
|
ServiceProvider,
|
||||||
} from "../constant";
|
} from "../constant";
|
||||||
import { createPersistStore } from "../utils/store";
|
import { createPersistStore } from "../utils/store";
|
||||||
|
|
||||||
export type ModelType = (typeof DEFAULT_MODELS)[number]["name"];
|
export type ModelType = (typeof DEFAULT_MODELS)[number]["name"];
|
||||||
|
export type TTSModelType = (typeof DEFAULT_TTS_MODELS)[number];
|
||||||
|
export type TTSVoiceType = (typeof DEFAULT_TTS_VOICES)[number];
|
||||||
|
export type TTSEngineType = (typeof DEFAULT_TTS_ENGINES)[number];
|
||||||
|
|
||||||
export enum SubmitKey {
|
export enum SubmitKey {
|
||||||
Enter = "Enter",
|
Enter = "Enter",
|
||||||
|
@ -41,6 +50,8 @@ export const DEFAULT_CONFIG = {
|
||||||
enableAutoGenerateTitle: true,
|
enableAutoGenerateTitle: true,
|
||||||
sidebarWidth: DEFAULT_SIDEBAR_WIDTH,
|
sidebarWidth: DEFAULT_SIDEBAR_WIDTH,
|
||||||
|
|
||||||
|
enableArtifacts: true, // show artifacts config
|
||||||
|
|
||||||
disablePromptHint: false,
|
disablePromptHint: false,
|
||||||
|
|
||||||
dontShowMaskSplashScreen: false, // dont show splash screen when create chat
|
dontShowMaskSplashScreen: false, // dont show splash screen when create chat
|
||||||
|
@ -68,11 +79,21 @@ export const DEFAULT_CONFIG = {
|
||||||
quality: "standard" as DalleQuality,
|
quality: "standard" as DalleQuality,
|
||||||
style: "vivid" as DalleStyle,
|
style: "vivid" as DalleStyle,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
ttsConfig: {
|
||||||
|
enable: false,
|
||||||
|
autoplay: false,
|
||||||
|
engine: DEFAULT_TTS_ENGINE,
|
||||||
|
model: DEFAULT_TTS_MODEL,
|
||||||
|
voice: DEFAULT_TTS_VOICE,
|
||||||
|
speed: 1.0,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ChatConfig = typeof DEFAULT_CONFIG;
|
export type ChatConfig = typeof DEFAULT_CONFIG;
|
||||||
|
|
||||||
export type ModelConfig = ChatConfig["modelConfig"];
|
export type ModelConfig = ChatConfig["modelConfig"];
|
||||||
|
export type TTSConfig = ChatConfig["ttsConfig"];
|
||||||
|
|
||||||
export function limitNumber(
|
export function limitNumber(
|
||||||
x: number,
|
x: number,
|
||||||
|
@ -87,6 +108,21 @@ export function limitNumber(
|
||||||
return Math.min(max, Math.max(min, x));
|
return Math.min(max, Math.max(min, x));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const TTSConfigValidator = {
|
||||||
|
engine(x: string) {
|
||||||
|
return x as TTSEngineType;
|
||||||
|
},
|
||||||
|
model(x: string) {
|
||||||
|
return x as TTSModelType;
|
||||||
|
},
|
||||||
|
voice(x: string) {
|
||||||
|
return x as TTSVoiceType;
|
||||||
|
},
|
||||||
|
speed(x: number) {
|
||||||
|
return limitNumber(x, 0.25, 4.0, 1.0);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export const ModalConfigValidator = {
|
export const ModalConfigValidator = {
|
||||||
model(x: string) {
|
model(x: string) {
|
||||||
return x as ModelType;
|
return x as ModelType;
|
||||||
|
@ -143,6 +179,21 @@ export const useAppConfig = createPersistStore(
|
||||||
{
|
{
|
||||||
name: StoreKey.Config,
|
name: StoreKey.Config,
|
||||||
version: 4,
|
version: 4,
|
||||||
|
|
||||||
|
merge(persistedState, currentState) {
|
||||||
|
const state = persistedState as ChatConfig | undefined;
|
||||||
|
if (!state) return { ...currentState };
|
||||||
|
const models = currentState.models.slice();
|
||||||
|
state.models.forEach((pModel) => {
|
||||||
|
const idx = models.findIndex(
|
||||||
|
(v) => v.name === pModel.name && v.provider === pModel.provider,
|
||||||
|
);
|
||||||
|
if (idx !== -1) models[idx] = pModel;
|
||||||
|
else models.push(pModel);
|
||||||
|
});
|
||||||
|
return { ...currentState, ...state, models: models };
|
||||||
|
},
|
||||||
|
|
||||||
migrate(persistedState, version) {
|
migrate(persistedState, version) {
|
||||||
const state = persistedState as ChatConfig;
|
const state = persistedState as ChatConfig;
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
import OpenAPIClientAxios from "openapi-client-axios";
|
import OpenAPIClientAxios from "openapi-client-axios";
|
||||||
import { getLang, Lang } from "../locales";
|
|
||||||
import { StoreKey } from "../constant";
|
import { StoreKey } from "../constant";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
import { createPersistStore } from "../utils/store";
|
import { createPersistStore } from "../utils/store";
|
||||||
|
import { getClientConfig } from "../config/client";
|
||||||
import yaml from "js-yaml";
|
import yaml from "js-yaml";
|
||||||
import { adapter } from "../utils";
|
import { adapter, getOperationId } from "../utils";
|
||||||
|
import { useAccessStore } from "./access";
|
||||||
|
|
||||||
|
const isApp = getClientConfig()?.isApp;
|
||||||
|
|
||||||
export type Plugin = {
|
export type Plugin = {
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -17,7 +20,6 @@ export type Plugin = {
|
||||||
authLocation?: string;
|
authLocation?: string;
|
||||||
authHeader?: string;
|
authHeader?: string;
|
||||||
authToken?: string;
|
authToken?: string;
|
||||||
usingProxy?: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FunctionToolItem = {
|
export type FunctionToolItem = {
|
||||||
|
@ -47,18 +49,25 @@ export const FunctionToolService = {
|
||||||
plugin?.authType == "basic"
|
plugin?.authType == "basic"
|
||||||
? `Basic ${plugin?.authToken}`
|
? `Basic ${plugin?.authToken}`
|
||||||
: plugin?.authType == "bearer"
|
: plugin?.authType == "bearer"
|
||||||
? ` Bearer ${plugin?.authToken}`
|
? `Bearer ${plugin?.authToken}`
|
||||||
: plugin?.authToken;
|
: plugin?.authToken;
|
||||||
const authLocation = plugin?.authLocation || "header";
|
const authLocation = plugin?.authLocation || "header";
|
||||||
const definition = yaml.load(plugin.content) as any;
|
const definition = yaml.load(plugin.content) as any;
|
||||||
const serverURL = definition?.servers?.[0]?.url;
|
const serverURL = definition?.servers?.[0]?.url;
|
||||||
const baseURL = !!plugin?.usingProxy ? "/api/proxy" : serverURL;
|
const baseURL = !isApp ? "/api/proxy" : serverURL;
|
||||||
const headers: Record<string, string | undefined> = {
|
const headers: Record<string, string | undefined> = {
|
||||||
"X-Base-URL": !!plugin?.usingProxy ? serverURL : undefined,
|
"X-Base-URL": !isApp ? serverURL : undefined,
|
||||||
};
|
};
|
||||||
if (authLocation == "header") {
|
if (authLocation == "header") {
|
||||||
headers[headerName] = tokenValue;
|
headers[headerName] = tokenValue;
|
||||||
}
|
}
|
||||||
|
// try using openaiApiKey for Dalle3 Plugin.
|
||||||
|
if (!tokenValue && plugin.id === "dalle3") {
|
||||||
|
const openaiApiKey = useAccessStore.getState().openaiApiKey;
|
||||||
|
if (openaiApiKey) {
|
||||||
|
headers[headerName] = `Bearer ${openaiApiKey}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
const api = new OpenAPIClientAxios({
|
const api = new OpenAPIClientAxios({
|
||||||
definition: yaml.load(plugin.content) as any,
|
definition: yaml.load(plugin.content) as any,
|
||||||
axiosConfigDefaults: {
|
axiosConfigDefaults: {
|
||||||
|
@ -107,7 +116,7 @@ export const FunctionToolService = {
|
||||||
return {
|
return {
|
||||||
type: "function",
|
type: "function",
|
||||||
function: {
|
function: {
|
||||||
name: o.operationId,
|
name: getOperationId(o),
|
||||||
description: o.description || o.summary,
|
description: o.description || o.summary,
|
||||||
parameters: parameters,
|
parameters: parameters,
|
||||||
},
|
},
|
||||||
|
@ -115,7 +124,7 @@ export const FunctionToolService = {
|
||||||
}),
|
}),
|
||||||
funcs: operations.reduce((s, o) => {
|
funcs: operations.reduce((s, o) => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
s[o.operationId] = function (args) {
|
s[getOperationId(o)] = function (args) {
|
||||||
const parameters: Record<string, any> = {};
|
const parameters: Record<string, any> = {};
|
||||||
if (o.parameters instanceof Array) {
|
if (o.parameters instanceof Array) {
|
||||||
o.parameters.forEach((p) => {
|
o.parameters.forEach((p) => {
|
||||||
|
@ -130,8 +139,8 @@ export const FunctionToolService = {
|
||||||
} else if (authLocation == "body") {
|
} else if (authLocation == "body") {
|
||||||
args[headerName] = tokenValue;
|
args[headerName] = tokenValue;
|
||||||
}
|
}
|
||||||
// @ts-ignore
|
// @ts-ignore if o.operationId is null, then using o.path and o.method
|
||||||
return api.client[o.operationId](
|
return api.client.paths[o.path][o.method](
|
||||||
parameters,
|
parameters,
|
||||||
args,
|
args,
|
||||||
api.axiosConfigDefaults,
|
api.axiosConfigDefaults,
|
||||||
|
@ -166,7 +175,7 @@ export const usePluginStore = createPersistStore(
|
||||||
(set, get) => ({
|
(set, get) => ({
|
||||||
create(plugin?: Partial<Plugin>) {
|
create(plugin?: Partial<Plugin>) {
|
||||||
const plugins = get().plugins;
|
const plugins = get().plugins;
|
||||||
const id = nanoid();
|
const id = plugin?.id || nanoid();
|
||||||
plugins[id] = {
|
plugins[id] = {
|
||||||
...createEmptyPlugin(),
|
...createEmptyPlugin(),
|
||||||
...plugin,
|
...plugin,
|
||||||
|
@ -221,5 +230,42 @@ export const usePluginStore = createPersistStore(
|
||||||
{
|
{
|
||||||
name: StoreKey.Plugin,
|
name: StoreKey.Plugin,
|
||||||
version: 1,
|
version: 1,
|
||||||
|
onRehydrateStorage(state) {
|
||||||
|
// Skip store rehydration on server side
|
||||||
|
if (typeof window === "undefined") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch("./plugins.json")
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((res) => {
|
||||||
|
Promise.all(
|
||||||
|
res.map((item: any) =>
|
||||||
|
// skip get schema
|
||||||
|
state.get(item.id)
|
||||||
|
? item
|
||||||
|
: fetch(item.schema)
|
||||||
|
.then((res) => res.text())
|
||||||
|
.then((content) => ({
|
||||||
|
...item,
|
||||||
|
content,
|
||||||
|
}))
|
||||||
|
.catch((e) => item),
|
||||||
|
),
|
||||||
|
).then((builtinPlugins: any) => {
|
||||||
|
builtinPlugins
|
||||||
|
.filter((item: any) => item?.content)
|
||||||
|
.forEach((item: any) => {
|
||||||
|
const plugin = state.create(item);
|
||||||
|
state.updatePlugin(plugin.id, (plugin) => {
|
||||||
|
const tool = FunctionToolService.add(plugin, true);
|
||||||
|
plugin.title = tool.api.definition.info.title;
|
||||||
|
plugin.version = tool.api.definition.info.version;
|
||||||
|
plugin.builtin = true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import Fuse from "fuse.js";
|
import Fuse from "fuse.js";
|
||||||
import { getLang } from "../locales";
|
|
||||||
import { StoreKey } from "../constant";
|
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
|
import { StoreKey } from "../constant";
|
||||||
|
import { getLang } from "../locales";
|
||||||
import { createPersistStore } from "../utils/store";
|
import { createPersistStore } from "../utils/store";
|
||||||
|
|
||||||
export interface Prompt {
|
export interface Prompt {
|
||||||
|
@ -147,6 +147,11 @@ export const usePromptStore = createPersistStore(
|
||||||
},
|
},
|
||||||
|
|
||||||
onRehydrateStorage(state) {
|
onRehydrateStorage(state) {
|
||||||
|
// Skip store rehydration on server side
|
||||||
|
if (typeof window === "undefined") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const PROMPT_URL = "./prompts.json";
|
const PROMPT_URL = "./prompts.json";
|
||||||
|
|
||||||
type PromptList = Array<[string, string]>;
|
type PromptList = Array<[string, string]>;
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import { getClientConfig } from "../config/client";
|
import { getClientConfig } from "../config/client";
|
||||||
import { Updater } from "../typing";
|
|
||||||
import { ApiPath, STORAGE_KEY, StoreKey } from "../constant";
|
import { ApiPath, STORAGE_KEY, StoreKey } from "../constant";
|
||||||
import { createPersistStore } from "../utils/store";
|
import { createPersistStore } from "../utils/store";
|
||||||
import {
|
import {
|
||||||
|
@ -100,15 +99,17 @@ export const useSyncStore = createPersistStore(
|
||||||
const remoteState = await client.get(config.username);
|
const remoteState = await client.get(config.username);
|
||||||
if (!remoteState || remoteState === "") {
|
if (!remoteState || remoteState === "") {
|
||||||
await client.set(config.username, JSON.stringify(localState));
|
await client.set(config.username, JSON.stringify(localState));
|
||||||
console.log("[Sync] Remote state is empty, using local state instead.");
|
console.log(
|
||||||
return
|
"[Sync] Remote state is empty, using local state instead.",
|
||||||
|
);
|
||||||
|
return;
|
||||||
} else {
|
} else {
|
||||||
const parsedRemoteState = JSON.parse(
|
const parsedRemoteState = JSON.parse(
|
||||||
await client.get(config.username),
|
await client.get(config.username),
|
||||||
) as AppState;
|
) as AppState;
|
||||||
mergeAppState(localState, parsedRemoteState);
|
mergeAppState(localState, parsedRemoteState);
|
||||||
setLocalAppState(localState);
|
setLocalAppState(localState);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log("[Sync] failed to get remote state", e);
|
console.log("[Sync] failed to get remote state", e);
|
||||||
throw e;
|
throw e;
|
||||||
|
|
|
@ -8,8 +8,6 @@ import { getClientConfig } from "../config/client";
|
||||||
import { createPersistStore } from "../utils/store";
|
import { createPersistStore } from "../utils/store";
|
||||||
import ChatGptIcon from "../icons/chatgpt.png";
|
import ChatGptIcon from "../icons/chatgpt.png";
|
||||||
import Locale from "../locales";
|
import Locale from "../locales";
|
||||||
import { use } from "react";
|
|
||||||
import { useAppConfig } from ".";
|
|
||||||
import { ClientApi } from "../client/api";
|
import { ClientApi } from "../client/api";
|
||||||
|
|
||||||
const ONE_MINUTE = 60 * 1000;
|
const ONE_MINUTE = 60 * 1000;
|
||||||
|
|
15
app/utils.ts
15
app/utils.ts
|
@ -3,8 +3,7 @@ import { showToast } from "./components/ui-lib";
|
||||||
import Locale from "./locales";
|
import Locale from "./locales";
|
||||||
import { RequestMessage } from "./client/api";
|
import { RequestMessage } from "./client/api";
|
||||||
import { ServiceProvider, REQUEST_TIMEOUT_MS } from "./constant";
|
import { ServiceProvider, REQUEST_TIMEOUT_MS } from "./constant";
|
||||||
import isObject from "lodash-es/isObject";
|
import { fetch as tauriFetch, ResponseType } from "@tauri-apps/api/http";
|
||||||
import { fetch as tauriFetch, Body, ResponseType } from "@tauri-apps/api/http";
|
|
||||||
|
|
||||||
export function trimTopic(topic: string) {
|
export function trimTopic(topic: string) {
|
||||||
// Fix an issue where double quotes still show in the Indonesian language
|
// Fix an issue where double quotes still show in the Indonesian language
|
||||||
|
@ -378,3 +377,15 @@ export function safeLocalStorage(): {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getOperationId(operation: {
|
||||||
|
operationId?: string;
|
||||||
|
method: string;
|
||||||
|
path: string;
|
||||||
|
}) {
|
||||||
|
// pattern '^[a-zA-Z0-9_-]+$'
|
||||||
|
return (
|
||||||
|
operation?.operationId ||
|
||||||
|
`${operation.method.toUpperCase()}${operation.path.replaceAll("/", "_")}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
type TTSPlayer = {
|
||||||
|
init: () => void;
|
||||||
|
play: (audioBuffer: ArrayBuffer, onended: () => void | null) => Promise<void>;
|
||||||
|
stop: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function createTTSPlayer(): TTSPlayer {
|
||||||
|
let audioContext: AudioContext | null = null;
|
||||||
|
let audioBufferSourceNode: AudioBufferSourceNode | null = null;
|
||||||
|
|
||||||
|
const init = () => {
|
||||||
|
audioContext = new (window.AudioContext || window.webkitAudioContext)();
|
||||||
|
audioContext.suspend();
|
||||||
|
};
|
||||||
|
|
||||||
|
const play = async (audioBuffer: ArrayBuffer, onended: () => void | null) => {
|
||||||
|
if (audioBufferSourceNode) {
|
||||||
|
audioBufferSourceNode.stop();
|
||||||
|
audioBufferSourceNode.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
const buffer = await audioContext!.decodeAudioData(audioBuffer);
|
||||||
|
audioBufferSourceNode = audioContext!.createBufferSource();
|
||||||
|
audioBufferSourceNode.buffer = buffer;
|
||||||
|
audioBufferSourceNode.connect(audioContext!.destination);
|
||||||
|
audioContext!.resume().then(() => {
|
||||||
|
audioBufferSourceNode!.start();
|
||||||
|
});
|
||||||
|
audioBufferSourceNode.onended = onended;
|
||||||
|
};
|
||||||
|
|
||||||
|
const stop = () => {
|
||||||
|
if (audioBufferSourceNode) {
|
||||||
|
audioBufferSourceNode.stop();
|
||||||
|
audioBufferSourceNode.disconnect();
|
||||||
|
audioBufferSourceNode = null;
|
||||||
|
}
|
||||||
|
if (audioContext) {
|
||||||
|
audioContext.close();
|
||||||
|
audioContext = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return { init, play, stop };
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { sendGAEvent } from "@next/third-parties/google";
|
||||||
|
|
||||||
|
export function trackConversationGuideToCPaymentClick() {
|
||||||
|
sendGAEvent("event", "ConversationGuideToCPaymentClick", { value: 1 });
|
||||||
|
}
|
||||||
|
|
||||||
|
export function trackAuthorizationPageButtonToCPaymentClick() {
|
||||||
|
sendGAEvent("event", "AuthorizationPageButtonToCPaymentClick", { value: 1 });
|
||||||
|
}
|
||||||
|
|
||||||
|
export function trackAuthorizationPageBannerToCPaymentClick() {
|
||||||
|
sendGAEvent("event", "AuthorizationPageBannerToCPaymentClick", {
|
||||||
|
value: 1,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function trackSettingsPageGuideToCPaymentClick() {
|
||||||
|
sendGAEvent("event", "SettingsPageGuideToCPaymentClick", { value: 1 });
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
import { getClientConfig } from "../config/client";
|
import { getClientConfig } from "../config/client";
|
||||||
import { ApiPath, DEFAULT_API_HOST } from "../constant";
|
import { DEFAULT_API_HOST } from "../constant";
|
||||||
|
|
||||||
export function corsPath(path: string) {
|
export function corsPath(path: string) {
|
||||||
const baseUrl = getClientConfig()?.isApp ? `${DEFAULT_API_HOST}` : "";
|
const baseUrl = getClientConfig()?.isApp ? `${DEFAULT_API_HOST}` : "";
|
||||||
|
|
|
@ -0,0 +1,391 @@
|
||||||
|
// import axios from "axios";
|
||||||
|
import { Buffer } from "buffer";
|
||||||
|
import { randomBytes } from "crypto";
|
||||||
|
import { Readable } from "stream";
|
||||||
|
|
||||||
|
// Modified according to https://github.com/Migushthe2nd/MsEdgeTTS
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://learn.microsoft.com/en-us/azure/ai-services/speech-service/speech-synthesis-markup-voice#:~:text=Optional-,volume,-Indicates%20the%20volume
|
||||||
|
*/
|
||||||
|
export enum VOLUME {
|
||||||
|
SILENT = "silent",
|
||||||
|
X_SOFT = "x-soft",
|
||||||
|
SOFT = "soft",
|
||||||
|
MEDIUM = "medium",
|
||||||
|
LOUD = "loud",
|
||||||
|
X_LOUD = "x-LOUD",
|
||||||
|
DEFAULT = "default",
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://learn.microsoft.com/en-us/azure/ai-services/speech-service/speech-synthesis-markup-voice#:~:text=Optional-,rate,-Indicates%20the%20speaking
|
||||||
|
*/
|
||||||
|
export enum RATE {
|
||||||
|
X_SLOW = "x-slow",
|
||||||
|
SLOW = "slow",
|
||||||
|
MEDIUM = "medium",
|
||||||
|
FAST = "fast",
|
||||||
|
X_FAST = "x-fast",
|
||||||
|
DEFAULT = "default",
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://learn.microsoft.com/en-us/azure/ai-services/speech-service/speech-synthesis-markup-voice#:~:text=Optional-,pitch,-Indicates%20the%20baseline
|
||||||
|
*/
|
||||||
|
export enum PITCH {
|
||||||
|
X_LOW = "x-low",
|
||||||
|
LOW = "low",
|
||||||
|
MEDIUM = "medium",
|
||||||
|
HIGH = "high",
|
||||||
|
X_HIGH = "x-high",
|
||||||
|
DEFAULT = "default",
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Only a few of the [possible formats](https://docs.microsoft.com/en-us/azure/cognitive-services/speech-service/rest-text-to-speech#audio-outputs) are accepted.
|
||||||
|
*/
|
||||||
|
export enum OUTPUT_FORMAT {
|
||||||
|
// Streaming =============================
|
||||||
|
// AMR_WB_16000HZ = "amr-wb-16000hz",
|
||||||
|
// AUDIO_16KHZ_16BIT_32KBPS_MONO_OPUS = "audio-16khz-16bit-32kbps-mono-opus",
|
||||||
|
// AUDIO_16KHZ_32KBITRATE_MONO_MP3 = "audio-16khz-32kbitrate-mono-mp3",
|
||||||
|
// AUDIO_16KHZ_64KBITRATE_MONO_MP3 = "audio-16khz-64kbitrate-mono-mp3",
|
||||||
|
// AUDIO_16KHZ_128KBITRATE_MONO_MP3 = "audio-16khz-128kbitrate-mono-mp3",
|
||||||
|
// AUDIO_24KHZ_16BIT_24KBPS_MONO_OPUS = "audio-24khz-16bit-24kbps-mono-opus",
|
||||||
|
// AUDIO_24KHZ_16BIT_48KBPS_MONO_OPUS = "audio-24khz-16bit-48kbps-mono-opus",
|
||||||
|
AUDIO_24KHZ_48KBITRATE_MONO_MP3 = "audio-24khz-48kbitrate-mono-mp3",
|
||||||
|
AUDIO_24KHZ_96KBITRATE_MONO_MP3 = "audio-24khz-96kbitrate-mono-mp3",
|
||||||
|
// AUDIO_24KHZ_160KBITRATE_MONO_MP3 = "audio-24khz-160kbitrate-mono-mp3",
|
||||||
|
// AUDIO_48KHZ_96KBITRATE_MONO_MP3 = "audio-48khz-96kbitrate-mono-mp3",
|
||||||
|
// AUDIO_48KHZ_192KBITRATE_MONO_MP3 = "audio-48khz-192kbitrate-mono-mp3",
|
||||||
|
// OGG_16KHZ_16BIT_MONO_OPUS = "ogg-16khz-16bit-mono-opus",
|
||||||
|
// OGG_24KHZ_16BIT_MONO_OPUS = "ogg-24khz-16bit-mono-opus",
|
||||||
|
// OGG_48KHZ_16BIT_MONO_OPUS = "ogg-48khz-16bit-mono-opus",
|
||||||
|
// RAW_8KHZ_8BIT_MONO_ALAW = "raw-8khz-8bit-mono-alaw",
|
||||||
|
// RAW_8KHZ_8BIT_MONO_MULAW = "raw-8khz-8bit-mono-mulaw",
|
||||||
|
// RAW_8KHZ_16BIT_MONO_PCM = "raw-8khz-16bit-mono-pcm",
|
||||||
|
// RAW_16KHZ_16BIT_MONO_PCM = "raw-16khz-16bit-mono-pcm",
|
||||||
|
// RAW_16KHZ_16BIT_MONO_TRUESILK = "raw-16khz-16bit-mono-truesilk",
|
||||||
|
// RAW_22050HZ_16BIT_MONO_PCM = "raw-22050hz-16bit-mono-pcm",
|
||||||
|
// RAW_24KHZ_16BIT_MONO_PCM = "raw-24khz-16bit-mono-pcm",
|
||||||
|
// RAW_24KHZ_16BIT_MONO_TRUESILK = "raw-24khz-16bit-mono-truesilk",
|
||||||
|
// RAW_44100HZ_16BIT_MONO_PCM = "raw-44100hz-16bit-mono-pcm",
|
||||||
|
// RAW_48KHZ_16BIT_MONO_PCM = "raw-48khz-16bit-mono-pcm",
|
||||||
|
// WEBM_16KHZ_16BIT_MONO_OPUS = "webm-16khz-16bit-mono-opus",
|
||||||
|
// WEBM_24KHZ_16BIT_24KBPS_MONO_OPUS = "webm-24khz-16bit-24kbps-mono-opus",
|
||||||
|
WEBM_24KHZ_16BIT_MONO_OPUS = "webm-24khz-16bit-mono-opus",
|
||||||
|
// Non-streaming =============================
|
||||||
|
// RIFF_8KHZ_8BIT_MONO_ALAW = "riff-8khz-8bit-mono-alaw",
|
||||||
|
// RIFF_8KHZ_8BIT_MONO_MULAW = "riff-8khz-8bit-mono-mulaw",
|
||||||
|
// RIFF_8KHZ_16BIT_MONO_PCM = "riff-8khz-16bit-mono-pcm",
|
||||||
|
// RIFF_22050HZ_16BIT_MONO_PCM = "riff-22050hz-16bit-mono-pcm",
|
||||||
|
// RIFF_24KHZ_16BIT_MONO_PCM = "riff-24khz-16bit-mono-pcm",
|
||||||
|
// RIFF_44100HZ_16BIT_MONO_PCM = "riff-44100hz-16bit-mono-pcm",
|
||||||
|
// RIFF_48KHZ_16BIT_MONO_PCM = "riff-48khz-16bit-mono-pcm",
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Voice = {
|
||||||
|
Name: string;
|
||||||
|
ShortName: string;
|
||||||
|
Gender: string;
|
||||||
|
Locale: string;
|
||||||
|
SuggestedCodec: string;
|
||||||
|
FriendlyName: string;
|
||||||
|
Status: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class ProsodyOptions {
|
||||||
|
/**
|
||||||
|
* The pitch to use.
|
||||||
|
* Can be any {@link PITCH}, or a relative frequency in Hz (+50Hz), a relative semitone (+2st), or a relative percentage (+50%).
|
||||||
|
* [SSML documentation](https://learn.microsoft.com/en-us/azure/ai-services/speech-service/speech-synthesis-markup-voice#:~:text=Optional-,pitch,-Indicates%20the%20baseline)
|
||||||
|
*/
|
||||||
|
pitch?: PITCH | string = "+0Hz";
|
||||||
|
/**
|
||||||
|
* The rate to use.
|
||||||
|
* Can be any {@link RATE}, or a relative number (0.5), or string with a relative percentage (+50%).
|
||||||
|
* [SSML documentation](https://learn.microsoft.com/en-us/azure/ai-services/speech-service/speech-synthesis-markup-voice#:~:text=Optional-,rate,-Indicates%20the%20speaking)
|
||||||
|
*/
|
||||||
|
rate?: RATE | string | number = 1.0;
|
||||||
|
/**
|
||||||
|
* The volume to use.
|
||||||
|
* Can be any {@link VOLUME}, or an absolute number (0, 100), a string with a relative number (+50), or a relative percentage (+50%).
|
||||||
|
* [SSML documentation](https://learn.microsoft.com/en-us/azure/ai-services/speech-service/speech-synthesis-markup-voice#:~:text=Optional-,volume,-Indicates%20the%20volume)
|
||||||
|
*/
|
||||||
|
volume?: VOLUME | string | number = 100.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MsEdgeTTS {
|
||||||
|
static OUTPUT_FORMAT = OUTPUT_FORMAT;
|
||||||
|
private static TRUSTED_CLIENT_TOKEN = "6A5AA1D4EAFF4E9FB37E23D68491D6F4";
|
||||||
|
private static VOICES_URL = `https://speech.platform.bing.com/consumer/speech/synthesize/readaloud/voices/list?trustedclienttoken=${MsEdgeTTS.TRUSTED_CLIENT_TOKEN}`;
|
||||||
|
private static SYNTH_URL = `wss://speech.platform.bing.com/consumer/speech/synthesize/readaloud/edge/v1?TrustedClientToken=${MsEdgeTTS.TRUSTED_CLIENT_TOKEN}`;
|
||||||
|
private static BINARY_DELIM = "Path:audio\r\n";
|
||||||
|
private static VOICE_LANG_REGEX = /\w{2}-\w{2}/;
|
||||||
|
private readonly _enableLogger;
|
||||||
|
private _ws: WebSocket | undefined;
|
||||||
|
private _voice: any;
|
||||||
|
private _voiceLocale: any;
|
||||||
|
private _outputFormat: any;
|
||||||
|
private _streams: { [key: string]: Readable } = {};
|
||||||
|
private _startTime = 0;
|
||||||
|
|
||||||
|
private _log(...o: any[]) {
|
||||||
|
if (this._enableLogger) {
|
||||||
|
console.log(...o);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new `MsEdgeTTS` instance.
|
||||||
|
*
|
||||||
|
* @param agent (optional, **NOT SUPPORTED IN BROWSER**) Use a custom http.Agent implementation like [https-proxy-agent](https://github.com/TooTallNate/proxy-agents) or [socks-proxy-agent](https://github.com/TooTallNate/proxy-agents/tree/main/packages/socks-proxy-agent).
|
||||||
|
* @param enableLogger=false whether to enable the built-in logger. This logs connections inits, disconnects, and incoming data to the console
|
||||||
|
*/
|
||||||
|
public constructor(enableLogger: boolean = false) {
|
||||||
|
this._enableLogger = enableLogger;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _send(message: any) {
|
||||||
|
for (let i = 1; i <= 3 && this._ws!.readyState !== this._ws!.OPEN; i++) {
|
||||||
|
if (i == 1) {
|
||||||
|
this._startTime = Date.now();
|
||||||
|
}
|
||||||
|
this._log("connecting: ", i);
|
||||||
|
await this._initClient();
|
||||||
|
}
|
||||||
|
this._ws!.send(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _initClient() {
|
||||||
|
this._ws = new WebSocket(MsEdgeTTS.SYNTH_URL);
|
||||||
|
|
||||||
|
this._ws.binaryType = "arraybuffer";
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this._ws!.onopen = () => {
|
||||||
|
this._log(
|
||||||
|
"Connected in",
|
||||||
|
(Date.now() - this._startTime) / 1000,
|
||||||
|
"seconds",
|
||||||
|
);
|
||||||
|
this._send(
|
||||||
|
`Content-Type:application/json; charset=utf-8\r\nPath:speech.config\r\n\r\n
|
||||||
|
{
|
||||||
|
"context": {
|
||||||
|
"synthesis": {
|
||||||
|
"audio": {
|
||||||
|
"metadataoptions": {
|
||||||
|
"sentenceBoundaryEnabled": "false",
|
||||||
|
"wordBoundaryEnabled": "false"
|
||||||
|
},
|
||||||
|
"outputFormat": "${this._outputFormat}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
).then(resolve);
|
||||||
|
};
|
||||||
|
this._ws!.onmessage = (m: any) => {
|
||||||
|
const buffer = Buffer.from(m.data as ArrayBuffer);
|
||||||
|
const message = buffer.toString();
|
||||||
|
const requestId = /X-RequestId:(.*?)\r\n/gm.exec(message)![1];
|
||||||
|
if (message.includes("Path:turn.start")) {
|
||||||
|
// start of turn, ignore
|
||||||
|
} else if (message.includes("Path:turn.end")) {
|
||||||
|
// end of turn, close stream
|
||||||
|
this._streams[requestId].push(null);
|
||||||
|
} else if (message.includes("Path:response")) {
|
||||||
|
// context response, ignore
|
||||||
|
} else if (
|
||||||
|
message.includes("Path:audio") &&
|
||||||
|
m.data instanceof ArrayBuffer
|
||||||
|
) {
|
||||||
|
this._pushAudioData(buffer, requestId);
|
||||||
|
} else {
|
||||||
|
this._log("UNKNOWN MESSAGE", message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this._ws!.onclose = () => {
|
||||||
|
this._log(
|
||||||
|
"disconnected after:",
|
||||||
|
(Date.now() - this._startTime) / 1000,
|
||||||
|
"seconds",
|
||||||
|
);
|
||||||
|
for (const requestId in this._streams) {
|
||||||
|
this._streams[requestId].push(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this._ws!.onerror = function (error: any) {
|
||||||
|
reject("Connect Error: " + error);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _pushAudioData(audioBuffer: Buffer, requestId: string) {
|
||||||
|
const audioStartIndex =
|
||||||
|
audioBuffer.indexOf(MsEdgeTTS.BINARY_DELIM) +
|
||||||
|
MsEdgeTTS.BINARY_DELIM.length;
|
||||||
|
const audioData = audioBuffer.subarray(audioStartIndex);
|
||||||
|
this._streams[requestId].push(audioData);
|
||||||
|
this._log("received audio chunk, size: ", audioData?.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _SSMLTemplate(input: string, options: ProsodyOptions = {}): string {
|
||||||
|
// in case future updates to the edge API block these elements, we'll be concatenating strings.
|
||||||
|
options = { ...new ProsodyOptions(), ...options };
|
||||||
|
return `<speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis" xmlns:mstts="https://www.w3.org/2001/mstts" xml:lang="${this._voiceLocale}">
|
||||||
|
<voice name="${this._voice}">
|
||||||
|
<prosody pitch="${options.pitch}" rate="${options.rate}" volume="${options.volume}">
|
||||||
|
${input}
|
||||||
|
</prosody>
|
||||||
|
</voice>
|
||||||
|
</speak>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch the list of voices available in Microsoft Edge.
|
||||||
|
* These, however, are not all. The complete list of voices supported by this module [can be found here](https://docs.microsoft.com/en-us/azure/cognitive-services/speech-service/language-support) (neural, standard, and preview).
|
||||||
|
*/
|
||||||
|
// getVoices(): Promise<Voice[]> {
|
||||||
|
// return new Promise((resolve, reject) => {
|
||||||
|
// axios
|
||||||
|
// .get(MsEdgeTTS.VOICES_URL)
|
||||||
|
// .then((res) => resolve(res.data))
|
||||||
|
// .catch(reject);
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
getVoices(): Promise<Voice[]> {
|
||||||
|
return fetch(MsEdgeTTS.VOICES_URL)
|
||||||
|
.then((response) => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("Network response was not ok");
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then((data) => data as Voice[])
|
||||||
|
.catch((error) => {
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the required information for the speech to be synthesised and inits a new WebSocket connection.
|
||||||
|
* Must be called at least once before text can be synthesised.
|
||||||
|
* Saved in this instance. Can be called at any time times to update the metadata.
|
||||||
|
*
|
||||||
|
* @param voiceName a string with any `ShortName`. A list of all available neural voices can be found [here](https://docs.microsoft.com/en-us/azure/cognitive-services/speech-service/language-support#neural-voices). However, it is not limited to neural voices: standard voices can also be used. A list of standard voices can be found [here](https://docs.microsoft.com/en-us/azure/cognitive-services/speech-service/language-support#standard-voices)
|
||||||
|
* @param outputFormat any {@link OUTPUT_FORMAT}
|
||||||
|
* @param voiceLocale (optional) any voice locale that is supported by the voice. See the list of all voices for compatibility. If not provided, the locale will be inferred from the `voiceName`
|
||||||
|
*/
|
||||||
|
async setMetadata(
|
||||||
|
voiceName: string,
|
||||||
|
outputFormat: OUTPUT_FORMAT,
|
||||||
|
voiceLocale?: string,
|
||||||
|
) {
|
||||||
|
const oldVoice = this._voice;
|
||||||
|
const oldVoiceLocale = this._voiceLocale;
|
||||||
|
const oldOutputFormat = this._outputFormat;
|
||||||
|
|
||||||
|
this._voice = voiceName;
|
||||||
|
this._voiceLocale = voiceLocale;
|
||||||
|
if (!this._voiceLocale) {
|
||||||
|
const voiceLangMatch = MsEdgeTTS.VOICE_LANG_REGEX.exec(this._voice);
|
||||||
|
if (!voiceLangMatch)
|
||||||
|
throw new Error("Could not infer voiceLocale from voiceName!");
|
||||||
|
this._voiceLocale = voiceLangMatch[0];
|
||||||
|
}
|
||||||
|
this._outputFormat = outputFormat;
|
||||||
|
|
||||||
|
const changed =
|
||||||
|
oldVoice !== this._voice ||
|
||||||
|
oldVoiceLocale !== this._voiceLocale ||
|
||||||
|
oldOutputFormat !== this._outputFormat;
|
||||||
|
|
||||||
|
// create new client
|
||||||
|
if (changed || this._ws!.readyState !== this._ws!.OPEN) {
|
||||||
|
this._startTime = Date.now();
|
||||||
|
await this._initClient();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _metadataCheck() {
|
||||||
|
if (!this._ws)
|
||||||
|
throw new Error(
|
||||||
|
"Speech synthesis not configured yet. Run setMetadata before calling toStream or toFile.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close the WebSocket connection.
|
||||||
|
*/
|
||||||
|
close() {
|
||||||
|
this._ws!.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes raw audio synthesised from text in real-time to a {@link Readable}. Uses a basic {@link _SSMLTemplate SML template}.
|
||||||
|
*
|
||||||
|
* @param input the text to synthesise. Can include SSML elements.
|
||||||
|
* @param options (optional) {@link ProsodyOptions}
|
||||||
|
* @returns {Readable} - a `stream.Readable` with the audio data
|
||||||
|
*/
|
||||||
|
toStream(input: string, options?: ProsodyOptions): Readable {
|
||||||
|
const { stream } = this._rawSSMLRequest(this._SSMLTemplate(input, options));
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
toArrayBuffer(input: string, options?: ProsodyOptions): Promise<ArrayBuffer> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let data: Uint8Array[] = [];
|
||||||
|
const readable = this.toStream(input, options);
|
||||||
|
readable.on("data", (chunk) => {
|
||||||
|
data.push(chunk);
|
||||||
|
});
|
||||||
|
|
||||||
|
readable.on("end", () => {
|
||||||
|
resolve(Buffer.concat(data).buffer);
|
||||||
|
});
|
||||||
|
|
||||||
|
readable.on("error", (err) => {
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes raw audio synthesised from a request in real-time to a {@link Readable}. Has no SSML template. Basic SSML should be provided in the request.
|
||||||
|
*
|
||||||
|
* @param requestSSML the SSML to send. SSML elements required in order to work.
|
||||||
|
* @returns {Readable} - a `stream.Readable` with the audio data
|
||||||
|
*/
|
||||||
|
rawToStream(requestSSML: string): Readable {
|
||||||
|
const { stream } = this._rawSSMLRequest(requestSSML);
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _rawSSMLRequest(requestSSML: string): {
|
||||||
|
stream: Readable;
|
||||||
|
requestId: string;
|
||||||
|
} {
|
||||||
|
this._metadataCheck();
|
||||||
|
|
||||||
|
const requestId = randomBytes(16).toString("hex");
|
||||||
|
const request =
|
||||||
|
`X-RequestId:${requestId}\r\nContent-Type:application/ssml+xml\r\nPath:ssml\r\n\r\n
|
||||||
|
` + requestSSML.trim();
|
||||||
|
// https://docs.microsoft.com/en-us/azure/cognitive-services/speech-service/speech-synthesis-markup
|
||||||
|
const self = this;
|
||||||
|
const stream = new Readable({
|
||||||
|
read() {},
|
||||||
|
destroy(error: Error | null, callback: (error: Error | null) => void) {
|
||||||
|
delete self._streams[requestId];
|
||||||
|
callback(error);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
this._streams[requestId] = stream;
|
||||||
|
this._send(request).then();
|
||||||
|
return { stream, requestId };
|
||||||
|
}
|
||||||
|
}
|
|
@ -32,6 +32,7 @@
|
||||||
"idb-keyval": "^6.2.1",
|
"idb-keyval": "^6.2.1",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"mermaid": "^10.6.1",
|
"mermaid": "^10.6.1",
|
||||||
|
"markdown-to-txt": "^2.0.1",
|
||||||
"nanoid": "^5.0.3",
|
"nanoid": "^5.0.3",
|
||||||
"next": "^14.1.1",
|
"next": "^14.1.1",
|
||||||
"node-fetch": "^3.3.1",
|
"node-fetch": "^3.3.1",
|
||||||
|
@ -66,6 +67,7 @@
|
||||||
"eslint-config-next": "13.4.19",
|
"eslint-config-next": "13.4.19",
|
||||||
"eslint-config-prettier": "^8.8.0",
|
"eslint-config-prettier": "^8.8.0",
|
||||||
"eslint-plugin-prettier": "^5.1.3",
|
"eslint-plugin-prettier": "^5.1.3",
|
||||||
|
"eslint-plugin-unused-imports": "^3.2.0",
|
||||||
"husky": "^8.0.0",
|
"husky": "^8.0.0",
|
||||||
"lint-staged": "^13.2.2",
|
"lint-staged": "^13.2.2",
|
||||||
"prettier": "^3.0.2",
|
"prettier": "^3.0.2",
|
||||||
|
@ -78,4 +80,4 @@
|
||||||
"lint-staged/yaml": "^2.2.2"
|
"lint-staged/yaml": "^2.2.2"
|
||||||
},
|
},
|
||||||
"packageManager": "yarn@1.22.19"
|
"packageManager": "yarn@1.22.19"
|
||||||
}
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "dalle3",
|
||||||
|
"name": "Dalle3",
|
||||||
|
"schema": "https://ghp.ci/https://raw.githubusercontent.com/ChatGPTNextWeb/NextChat-Awesome-Plugins/main/plugins/dalle/openapi.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "arxivsearch",
|
||||||
|
"name": "ArxivSearch",
|
||||||
|
"schema": "https://ghp.ci/https://raw.githubusercontent.com/ChatGPTNextWeb/NextChat-Awesome-Plugins/main/plugins/arxivsearch/openapi.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "duckduckgolite",
|
||||||
|
"name": "DuckDuckGoLiteSearch",
|
||||||
|
"schema": "https://ghp.ci/https://raw.githubusercontent.com/ChatGPTNextWeb/NextChat-Awesome-Plugins/main/plugins/duckduckgolite/openapi.json"
|
||||||
|
}
|
||||||
|
]
|
|
@ -9,7 +9,7 @@
|
||||||
},
|
},
|
||||||
"package": {
|
"package": {
|
||||||
"productName": "NextChat",
|
"productName": "NextChat",
|
||||||
"version": "2.15.2"
|
"version": "2.15.3"
|
||||||
},
|
},
|
||||||
"tauri": {
|
"tauri": {
|
||||||
"allowlist": {
|
"allowlist": {
|
||||||
|
|
36
yarn.lock
36
yarn.lock
|
@ -3367,6 +3367,18 @@ eslint-plugin-react@^7.31.7:
|
||||||
semver "^6.3.0"
|
semver "^6.3.0"
|
||||||
string.prototype.matchall "^4.0.8"
|
string.prototype.matchall "^4.0.8"
|
||||||
|
|
||||||
|
eslint-plugin-unused-imports@^3.2.0:
|
||||||
|
version "3.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-3.2.0.tgz#63a98c9ad5f622cd9f830f70bc77739f25ccfe0d"
|
||||||
|
integrity sha512-6uXyn6xdINEpxE1MtDjxQsyXB37lfyO2yKGVVgtD7WEWQGORSOZjgrD6hBhvGv4/SO+TOlS+UnC6JppRqbuwGQ==
|
||||||
|
dependencies:
|
||||||
|
eslint-rule-composer "^0.3.0"
|
||||||
|
|
||||||
|
eslint-rule-composer@^0.3.0:
|
||||||
|
version "0.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/eslint-rule-composer/-/eslint-rule-composer-0.3.0.tgz#79320c927b0c5c0d3d3d2b76c8b4a488f25bbaf9"
|
||||||
|
integrity sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg==
|
||||||
|
|
||||||
eslint-scope@5.1.1:
|
eslint-scope@5.1.1:
|
||||||
version "5.1.1"
|
version "5.1.1"
|
||||||
resolved "https://registry.npmmirror.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c"
|
resolved "https://registry.npmmirror.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c"
|
||||||
|
@ -4443,11 +4455,21 @@ lodash.debounce@^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"
|
||||||
integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==
|
integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==
|
||||||
|
|
||||||
|
lodash.escape@^4.0.1:
|
||||||
|
version "4.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash.escape/-/lodash.escape-4.0.1.tgz#c9044690c21e04294beaa517712fded1fa88de98"
|
||||||
|
integrity sha512-nXEOnb/jK9g0DYMr1/Xvq6l5xMD7GDG55+GSYIYmS0G4tBk/hURD4JR9WCavs04t33WmJx9kCyp9vJ+mr4BOUw==
|
||||||
|
|
||||||
lodash.merge@^4.6.2:
|
lodash.merge@^4.6.2:
|
||||||
version "4.6.2"
|
version "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.unescape@^4.0.1:
|
||||||
|
version "4.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash.unescape/-/lodash.unescape-4.0.1.tgz#bf2249886ce514cda112fae9218cdc065211fc9c"
|
||||||
|
integrity sha512-DhhGRshNS1aX6s5YdBE3njCCouPgnG29ebyHvImlZzXZf2SHgt+J08DHgytTPnpywNbO1Y8mNUFyQuIDBq2JZg==
|
||||||
|
|
||||||
lodash@^4.17.21:
|
lodash@^4.17.21:
|
||||||
version "4.17.21"
|
version "4.17.21"
|
||||||
resolved "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
resolved "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
||||||
|
@ -4503,6 +4525,20 @@ markdown-table@^3.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/markdown-table/-/markdown-table-3.0.3.tgz#e6331d30e493127e031dd385488b5bd326e4a6bd"
|
resolved "https://registry.yarnpkg.com/markdown-table/-/markdown-table-3.0.3.tgz#e6331d30e493127e031dd385488b5bd326e4a6bd"
|
||||||
integrity sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==
|
integrity sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==
|
||||||
|
|
||||||
|
markdown-to-txt@^2.0.1:
|
||||||
|
version "2.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/markdown-to-txt/-/markdown-to-txt-2.0.1.tgz#bfd6233a2635443cc24900a158b60c6af36ce9c5"
|
||||||
|
integrity sha512-Hsj7KTN8k1gutlLum3vosHwVZGnv8/cbYKWVkUyo/D1rzOYddbDesILebRfOsaVfjIBJank/AVOySBlHAYqfZw==
|
||||||
|
dependencies:
|
||||||
|
lodash.escape "^4.0.1"
|
||||||
|
lodash.unescape "^4.0.1"
|
||||||
|
marked "^4.0.14"
|
||||||
|
|
||||||
|
marked@^4.0.14:
|
||||||
|
version "4.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/marked/-/marked-4.3.0.tgz#796362821b019f734054582038b116481b456cf3"
|
||||||
|
integrity sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==
|
||||||
|
|
||||||
mdast-util-definitions@^5.0.0:
|
mdast-util-definitions@^5.0.0:
|
||||||
version "5.1.2"
|
version "5.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/mdast-util-definitions/-/mdast-util-definitions-5.1.2.tgz#9910abb60ac5d7115d6819b57ae0bcef07a3f7a7"
|
resolved "https://registry.yarnpkg.com/mdast-util-definitions/-/mdast-util-definitions-5.1.2.tgz#9910abb60ac5d7115d6819b57ae0bcef07a3f7a7"
|
||||||
|
|
Loading…
Reference in New Issue