+
setLoadUrl(e.currentTarget.value)}
>
void }) {
const promptStore = usePromptStore();
@@ -685,6 +688,31 @@ export function Settings() {
);
+ const saasStartComponent = (
+
+ }
+ type={"primary"}
+ text={Locale.Settings.Access.SaasStart.ChatNow}
+ onClick={() => {
+ trackSettingsPageGuideToCPaymentClick();
+ window.location.href = SAAS_CHAT_URL;
+ }}
+ />
+
+ );
+
const useCustomConfigComponent = // Conditionally render the following ListItem based on clientConfig.isApp
!clientConfig?.isApp && ( // only show if isApp is false
+
+
+
+ updateConfig(
+ (config) =>
+ (config.enableArtifacts = e.currentTarget.checked),
+ )
+ }
+ >
+
@@ -1540,6 +1585,7 @@ export function Settings() {
+ {saasStartComponent}
{accessCodeComponent}
{!accessStore.hideUserApiKey && (
@@ -1646,6 +1692,17 @@ export function Settings() {
setShowPromptModal(false)} />
)}
+
+ {
+ const ttsConfig = { ...config.ttsConfig };
+ updater(ttsConfig);
+ config.update((config) => (config.ttsConfig = ttsConfig));
+ }}
+ />
+
+
diff --git a/app/components/sidebar.tsx b/app/components/sidebar.tsx
index 4ec0f8c84f5..493b1103beb 100644
--- a/app/components/sidebar.tsx
+++ b/app/components/sidebar.tsx
@@ -7,7 +7,6 @@ import SettingsIcon from "../icons/settings.svg";
import GithubIcon from "../icons/github.svg";
import ChatGptIcon from "../icons/chatgpt.svg";
import AddIcon from "../icons/add.svg";
-import CloseIcon from "../icons/close.svg";
import DeleteIcon from "../icons/delete.svg";
import MaskIcon from "../icons/mask.svg";
import DragIcon from "../icons/drag.svg";
@@ -254,11 +253,6 @@ export function SideBar(props: { className?: string }) {
{showPluginSelector && (
{
return {
title: item.name,
diff --git a/app/components/tts-config.tsx b/app/components/tts-config.tsx
new file mode 100644
index 00000000000..39ae85730c2
--- /dev/null
+++ b/app/components/tts-config.tsx
@@ -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 (
+ <>
+
+
+ props.updateConfig(
+ (config) => (config.enable = e.currentTarget.checked),
+ )
+ }
+ >
+
+ {/*
+
+ props.updateConfig(
+ (config) => (config.autoplay = e.currentTarget.checked),
+ )
+ }
+ >
+ */}
+
+
+
+ {props.ttsConfig.engine === DEFAULT_TTS_ENGINE && (
+ <>
+
+
+
+
+
+
+
+ {
+ props.updateConfig(
+ (config) =>
+ (config.speed = TTSConfigValidator.speed(
+ e.currentTarget.valueAsNumber,
+ )),
+ );
+ }}
+ >
+
+ >
+ )}
+ >
+ );
+}
diff --git a/app/components/tts.module.scss b/app/components/tts.module.scss
new file mode 100644
index 00000000000..ba9f382e40b
--- /dev/null
+++ b/app/components/tts.module.scss
@@ -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;
+ }
+ }
+ }
+ }
+}
diff --git a/app/components/ui-lib.module.scss b/app/components/ui-lib.module.scss
index 20bf62a188b..56aeac311ae 100644
--- a/app/components/ui-lib.module.scss
+++ b/app/components/ui-lib.module.scss
@@ -62,14 +62,14 @@
}
}
- &.vertical{
+ &.vertical {
flex-direction: column;
align-items: start;
- .list-header{
- .list-item-title{
+ .list-header {
+ .list-item-title {
margin-bottom: 5px;
}
- .list-item-sub-title{
+ .list-item-sub-title {
margin-bottom: 2px;
}
}
@@ -310,7 +310,7 @@
justify-content: center;
z-index: 999;
- .selector-item-disabled{
+ .selector-item-disabled {
opacity: 0.6;
}
@@ -336,3 +336,4 @@
}
}
}
+
diff --git a/app/config/server.ts b/app/config/server.ts
index 676b0174f24..6544fe5641d 100644
--- a/app/config/server.ts
+++ b/app/config/server.ts
@@ -154,8 +154,8 @@ export const getServerSideConfig = () => {
// `[Server Config] using ${randomIndex + 1} of ${apiKeys.length} api key`,
// );
- const allowedWebDevEndpoints = (
- process.env.WHITE_WEBDEV_ENDPOINTS ?? ""
+ const allowedWebDavEndpoints = (
+ process.env.WHITE_WEBDAV_ENDPOINTS ?? ""
).split(",");
return {
@@ -229,6 +229,6 @@ export const getServerSideConfig = () => {
disableFastLink: !!process.env.DISABLE_FAST_LINK,
customModels,
defaultModel,
- allowedWebDevEndpoints,
+ allowedWebDavEndpoints,
};
};
diff --git a/app/constant.ts b/app/constant.ts
index 3d33a047e90..a54a973daa6 100644
--- a/app/constant.ts
+++ b/app/constant.ts
@@ -1,5 +1,3 @@
-import path from "path";
-
export const OWNER = "ChatGPTNextWeb";
export const REPO = "ChatGPT-Next-Web";
export const REPO_URL = `https://github.com/${OWNER}/${REPO}`;
@@ -152,6 +150,7 @@ export const Anthropic = {
export const OpenaiPath = {
ChatPath: "v1/chat/completions",
+ SpeechPath: "v1/audio/speech",
ImagePath: "v1/images/generations",
UsagePath: "dashboard/billing/usage",
SubsPath: "dashboard/billing/subscription",
@@ -258,6 +257,20 @@ export const KnowledgeCutOffDate: Record = {
"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 = [
"gpt-3.5-turbo",
"gpt-3.5-turbo-1106",
@@ -279,7 +292,7 @@ const openaiModels = [
"gpt-4-1106-preview",
"dall-e-3",
"o1-mini",
- "o1-preview"
+ "o1-preview",
];
const googleModels = [
@@ -488,3 +501,6 @@ export const PLUGINS = [
{ name: "Stable Diffusion", path: Path.Sd },
{ 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";
diff --git a/app/icons/arrow.svg b/app/icons/arrow.svg
new file mode 100644
index 00000000000..ddd69e61472
--- /dev/null
+++ b/app/icons/arrow.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/app/icons/fire.svg b/app/icons/fire.svg
new file mode 100644
index 00000000000..446d532aaca
--- /dev/null
+++ b/app/icons/fire.svg
@@ -0,0 +1 @@
+
diff --git a/app/icons/logo.svg b/app/icons/logo.svg
new file mode 100644
index 00000000000..b80263b8638
--- /dev/null
+++ b/app/icons/logo.svg
@@ -0,0 +1,19 @@
+
diff --git a/app/icons/speak-stop.svg b/app/icons/speak-stop.svg
new file mode 100644
index 00000000000..926ae7bb3d6
--- /dev/null
+++ b/app/icons/speak-stop.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/app/icons/speak.svg b/app/icons/speak.svg
new file mode 100644
index 00000000000..e02212c9a42
--- /dev/null
+++ b/app/icons/speak.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/app/icons/voice-white.svg b/app/icons/voice-white.svg
new file mode 100644
index 00000000000..e7d5cbcc86f
--- /dev/null
+++ b/app/icons/voice-white.svg
@@ -0,0 +1,16 @@
+
diff --git a/app/layout.tsx b/app/layout.tsx
index fa087636a44..7d14cb88d70 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -41,7 +41,11 @@ export default function RootLayout({
name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"
/>
-
+
diff --git a/app/locales/ar.ts b/app/locales/ar.ts
index 464519e2055..e2fad3494c3 100644
--- a/app/locales/ar.ts
+++ b/app/locales/ar.ts
@@ -1,15 +1,21 @@
import { SubmitKey } from "../store/config";
import type { PartialLocaleType } from "./index";
import { getClientConfig } from "../config/client";
-
+import { SAAS_CHAT_UTM_URL } from "@/app/constant";
const isApp = !!getClientConfig()?.isApp;
const ar: PartialLocaleType = {
WIP: "قريبًا...",
Error: {
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: {
Title: "تحتاج إلى كلمة مرور",
@@ -18,6 +24,10 @@ const ar: PartialLocaleType = {
Input: "أدخل رمز الوصول هنا",
Confirm: "تأكيد",
Later: "في وقت لاحق",
+ Return: "عودة",
+ SaasTips: "الإعدادات معقدة، أريد استخدامه على الفور",
+ TopTips:
+ "🥳 عرض NextChat AI الأول، افتح الآن OpenAI o1, GPT-4o, Claude-3.5 وأحدث النماذج الكبيرة",
},
ChatItem: {
ChatItemCount: (count: number) => `${count} محادثة`,
@@ -281,6 +291,13 @@ const ar: PartialLocaleType = {
},
Access: {
+ SaasStart: {
+ Title: "استخدام NextChat AI",
+ Label: "(أفضل حل من حيث التكلفة)",
+ SubTitle:
+ "مدعوم رسميًا من NextChat، جاهز للاستخدام بدون إعداد، يدعم أحدث النماذج الكبيرة مثل OpenAI o1 و GPT-4o و Claude-3.5",
+ ChatNow: "الدردشة الآن",
+ },
AccessCode: {
Title: "كلمة المرور للوصول",
SubTitle: "قام المشرف بتمكين الوصول المشفر",
diff --git a/app/locales/bn.ts b/app/locales/bn.ts
index 945c442df57..f52f101ce35 100644
--- a/app/locales/bn.ts
+++ b/app/locales/bn.ts
@@ -1,15 +1,21 @@
import { SubmitKey } from "../store/config";
import type { PartialLocaleType } from "./index";
import { getClientConfig } from "../config/client";
-
+import { SAAS_CHAT_UTM_URL } from "@/app/constant";
const isApp = !!getClientConfig()?.isApp;
const bn: PartialLocaleType = {
WIP: "শীঘ্রই আসছে...",
Error: {
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: {
Title: "পাসওয়ার্ড প্রয়োজন",
@@ -18,6 +24,10 @@ const bn: PartialLocaleType = {
Input: "এখানে অ্যাক্সেস কোড লিখুন",
Confirm: "নিশ্চিত করুন",
Later: "পরে বলুন",
+ Return: "ফিরে আসা",
+ SaasTips: "কনফিগারেশন খুব কঠিন, আমি অবিলম্বে ব্যবহার করতে চাই",
+ TopTips:
+ "🥳 NextChat AI প্রথম প্রকাশের অফার, এখনই OpenAI o1, GPT-4o, Claude-3.5 এবং সর্বশেষ বড় মডেলগুলি আনলক করুন",
},
ChatItem: {
ChatItemCount: (count: number) => `${count} টি চ্যাট`,
@@ -284,6 +294,14 @@ const bn: PartialLocaleType = {
},
Access: {
+ SaasStart: {
+ Title: "NextChat AI ব্যবহার করুন",
+ Label: "(সেরা মূল্যসাশ্রয়ী সমাধান)",
+ SubTitle:
+ "NextChat কর্তৃক অফিসিয়াল রক্ষণাবেক্ষণ, শূন্য কনফিগারেশন ব্যবহার শুরু করুন, OpenAI o1, GPT-4o, Claude-3.5 সহ সর্বশেষ বড় মডেলগুলি সমর্থন করে",
+ ChatNow: "এখনই চ্যাট করুন",
+ },
+
AccessCode: {
Title: "অ্যাক্সেস পাসওয়ার্ড",
SubTitle: "অ্যাডমিন এনক্রিপ্টেড অ্যাক্সেস সক্রিয় করেছেন",
diff --git a/app/locales/cn.ts b/app/locales/cn.ts
index fcbdb6f627c..e5bcca0edbb 100644
--- a/app/locales/cn.ts
+++ b/app/locales/cn.ts
@@ -1,6 +1,6 @@
-import { ShortcutKeyModal } from "../components/chat";
import { getClientConfig } from "../config/client";
import { SubmitKey } from "../store/config";
+import { SAAS_CHAT_UTM_URL } from "@/app/constant";
const isApp = !!getClientConfig()?.isApp;
@@ -8,16 +8,26 @@ const cn = {
WIP: "该功能仍在开发中……",
Error: {
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: {
+ Return: "返回",
Title: "需要密码",
Tips: "管理员开启了密码验证,请在下方填入访问码",
- SubTips: "或者输入你的 OpenAI 或 Google API 密钥",
+ SubTips: "或者输入你的 OpenAI 或 Google AI 密钥",
Input: "在此处填写访问码",
Confirm: "确认",
Later: "稍后再说",
+ SaasTips: "配置太麻烦,想要立即使用",
+ TopTips:
+ "🥳 NextChat AI 首发优惠,立刻解锁 OpenAI o1, GPT-4o, Claude-3.5 等最新大模型",
},
ChatItem: {
ChatItemCount: (count: number) => `${count} 条对话`,
@@ -46,6 +56,8 @@ const cn = {
FullScreen: "全屏",
RefreshTitle: "刷新标题",
RefreshToast: "已发送刷新标题请求",
+ Speech: "朗读",
+ StopSpeech: "停止",
},
Commands: {
new: "新建聊天",
@@ -53,6 +65,7 @@ const cn = {
next: "下一个聊天",
prev: "上一个聊天",
clear: "清除上下文",
+ fork: "复制聊天",
del: "删除聊天",
},
InputActions: {
@@ -79,6 +92,8 @@ const cn = {
return inputHints + ",/ 触发补全,: 触发命令";
},
Send: "发送",
+ StartSpeak: "说话",
+ StopSpeak: "停止",
Config: {
Reset: "清除记忆",
SaveAs: "存为面具",
@@ -293,6 +308,13 @@ const cn = {
},
Access: {
+ SaasStart: {
+ Title: "使用 NextChat AI",
+ Label: "(性价比最高的方案)",
+ SubTitle:
+ "由 NextChat 官方维护, 零配置开箱即用,支持 OpenAI o1, GPT-4o, Claude-3.5 等最新大模型",
+ ChatNow: "立刻对话",
+ },
AccessCode: {
Title: "访问密码",
SubTitle: "管理员已开启加密访问",
@@ -356,7 +378,7 @@ const cn = {
ApiKey: {
Title: "API 密钥",
SubTitle: "从 Google AI 获取您的 API 密钥",
- Placeholder: "输入您的 Google AI Studio API 密钥",
+ Placeholder: "Google AI API KEY",
},
Endpoint: {
@@ -496,6 +518,26 @@ const cn = {
Title: "频率惩罚度 (frequency_penalty)",
SubTitle: "值越大,越有可能降低重复字词",
},
+ TTS: {
+ Enable: {
+ Title: "启用文本转语音",
+ SubTitle: "启用文本生成语音服务",
+ },
+ Autoplay: {
+ Title: "启用自动朗读",
+ SubTitle: "自动生成语音并播放,需先开启文本转语音开关",
+ },
+ Model: "模型",
+ Engine: "转换引擎",
+ Voice: {
+ Title: "声音",
+ SubTitle: "生成语音时使用的声音",
+ },
+ Speed: {
+ Title: "速度",
+ SubTitle: "生成语音的速度",
+ },
+ },
},
Store: {
DefaultTopic: "新的聊天",
diff --git a/app/locales/cs.ts b/app/locales/cs.ts
index 5a132b3ce6d..d62a2036710 100644
--- a/app/locales/cs.ts
+++ b/app/locales/cs.ts
@@ -1,15 +1,21 @@
import { SubmitKey } from "../store/config";
import type { PartialLocaleType } from "./index";
import { getClientConfig } from "../config/client";
-
+import { SAAS_CHAT_UTM_URL } from "@/app/constant";
const isApp = !!getClientConfig()?.isApp;
const cs: PartialLocaleType = {
WIP: "V přípravě...",
Error: {
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."
- : "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.",
+ ? `😆 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 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: {
Title: "Potřebné heslo",
@@ -18,6 +24,10 @@ const cs: PartialLocaleType = {
Input: "Zadejte přístupový kód zde",
Confirm: "Potvrdit",
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: {
ChatItemCount: (count: number) => `${count} konverzací`,
@@ -284,6 +294,14 @@ const cs: PartialLocaleType = {
},
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: {
Title: "Přístupový kód",
SubTitle: "Administrátor aktivoval šifrovaný přístup",
diff --git a/app/locales/de.ts b/app/locales/de.ts
index ebe7aff2d71..3490190a80a 100644
--- a/app/locales/de.ts
+++ b/app/locales/de.ts
@@ -1,15 +1,21 @@
import { SubmitKey } from "../store/config";
import type { PartialLocaleType } from "./index";
import { getClientConfig } from "../config/client";
-
+import { SAAS_CHAT_UTM_URL } from "@/app/constant";
const isApp = !!getClientConfig()?.isApp;
const de: PartialLocaleType = {
WIP: "In Bearbeitung...",
Error: {
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 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.",
+ ? `😆 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 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: {
Title: "Passwort erforderlich",
@@ -18,6 +24,11 @@ const de: PartialLocaleType = {
Input: "Geben Sie hier den Zugangscode ein",
Confirm: "Bestätigen",
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: {
ChatItemCount: (count: number) => `${count} Gespräche`,
@@ -291,6 +302,14 @@ const de: PartialLocaleType = {
},
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: {
Title: "Zugangscode",
SubTitle:
diff --git a/app/locales/en.ts b/app/locales/en.ts
index 049447569f7..1204575224a 100644
--- a/app/locales/en.ts
+++ b/app/locales/en.ts
@@ -1,7 +1,7 @@
import { getClientConfig } from "../config/client";
import { SubmitKey } from "../store/config";
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
const isApp = !!getClientConfig()?.isApp;
@@ -9,16 +9,26 @@ const en: LocaleType = {
WIP: "Coming Soon...",
Error: {
Unauthorized: isApp
- ? "Invalid API Key, please check it in [Settings](/#/settings) page."
- : "Unauthorized access, please enter access code in [auth](/#/auth) page, or enter your OpenAI API Key.",
+ ? `😆 Oops, there's an issue. No worries:
+ \\ 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: {
+ Return: "Return",
Title: "Need Access Code",
Tips: "Please enter access code below",
SubTips: "Or enter your OpenAI or Google API Key",
Input: "access code",
Confirm: "Confirm",
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: {
ChatItemCount: (count: number) => `${count} messages`,
@@ -47,6 +57,8 @@ const en: LocaleType = {
FullScreen: "FullScreen",
RefreshTitle: "Refresh Title",
RefreshToast: "Title refresh request sent",
+ Speech: "Play",
+ StopSpeech: "Stop",
},
Commands: {
new: "Start a new chat",
@@ -54,6 +66,7 @@ const en: LocaleType = {
next: "Next Chat",
prev: "Previous Chat",
clear: "Clear Context",
+ fork: "Copy Chat",
del: "Delete Chat",
},
InputActions: {
@@ -80,6 +93,8 @@ const en: LocaleType = {
return inputHints + ", / to search prompts, : to use commands";
},
Send: "Send",
+ StartSpeak: "Start Speak",
+ StopSpeak: "Stop Speak",
Config: {
Reset: "Reset to Default",
SaveAs: "Save as Mask",
@@ -296,6 +311,14 @@ const en: LocaleType = {
NoAccess: "Enter API Key to check balance",
},
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: {
Title: "Access Code",
SubTitle: "Access control Enabled",
@@ -456,7 +479,7 @@ const en: LocaleType = {
ApiKey: {
Title: "API Key",
SubTitle: "Obtain your API Key from Google AI",
- Placeholder: "Enter your Google AI Studio API Key",
+ Placeholder: "Google AI API Key",
},
Endpoint: {
@@ -502,6 +525,27 @@ const en: LocaleType = {
SubTitle:
"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: {
DefaultTopic: "New Conversation",
diff --git a/app/locales/es.ts b/app/locales/es.ts
index c1caae2d385..03af9b439ad 100644
--- a/app/locales/es.ts
+++ b/app/locales/es.ts
@@ -1,15 +1,21 @@
import { SubmitKey } from "../store/config";
import type { PartialLocaleType } from "./index";
import { getClientConfig } from "../config/client";
-
+import { SAAS_CHAT_UTM_URL } from "@/app/constant";
const isApp = !!getClientConfig()?.isApp;
const es: PartialLocaleType = {
WIP: "En construcción...",
Error: {
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 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.",
+ ? `😆 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 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: {
Title: "Se requiere contraseña",
@@ -18,6 +24,11 @@ const es: PartialLocaleType = {
Input: "Introduce el código de acceso aquí",
Confirm: "Confirmar",
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: {
ChatItemCount: (count: number) => `${count} conversaciones`,
@@ -294,6 +305,14 @@ const es: PartialLocaleType = {
},
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: {
Title: "Contraseña de acceso",
SubTitle: "El administrador ha habilitado el acceso encriptado",
diff --git a/app/locales/fr.ts b/app/locales/fr.ts
index 97fb79e3238..d25c60eb6c8 100644
--- a/app/locales/fr.ts
+++ b/app/locales/fr.ts
@@ -1,15 +1,21 @@
import { SubmitKey } from "../store/config";
import type { PartialLocaleType } from "./index";
import { getClientConfig } from "../config/client";
-
+import { SAAS_CHAT_UTM_URL } from "@/app/constant";
const isApp = !!getClientConfig()?.isApp;
const fr: PartialLocaleType = {
WIP: "Prochainement...",
Error: {
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)."
- : "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).",
+ ? `😆 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 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: {
Title: "Mot de passe requis",
@@ -18,6 +24,11 @@ const fr: PartialLocaleType = {
Input: "Entrez le code d'accès ici",
Confirm: "Confirmer",
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: {
ChatItemCount: (count: number) => `${count} conversations`,
@@ -294,6 +305,14 @@ const fr: PartialLocaleType = {
},
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: {
Title: "Mot de passe d'accès",
SubTitle: "L'administrateur a activé l'accès sécurisé",
diff --git a/app/locales/id.ts b/app/locales/id.ts
index 1eff667ed9e..af96fd2725c 100644
--- a/app/locales/id.ts
+++ b/app/locales/id.ts
@@ -1,15 +1,21 @@
import { SubmitKey } from "../store/config";
import type { PartialLocaleType } from "./index";
import { getClientConfig } from "../config/client";
-
+import { SAAS_CHAT_UTM_URL } from "@/app/constant";
const isApp = !!getClientConfig()?.isApp;
const id: PartialLocaleType = {
WIP: "Coming Soon...",
Error: {
Unauthorized: isApp
- ? "API Key tidak valid terdeteksi, silakan periksa apakah API Key telah dikonfigurasi dengan benar di halaman [Pengaturan](/#/settings)."
- : "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).",
+ ? `😆 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 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: {
Title: "Kebutuhan Kata Sandi",
@@ -18,6 +24,10 @@ const id: PartialLocaleType = {
Input: "Masukkan kode akses di sini",
Confirm: "Konfirmasi",
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: {
ChatItemCount: (count: number) => `${count} percakapan`,
@@ -285,6 +295,14 @@ const id: PartialLocaleType = {
},
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: {
Title: "Kata Sandi Akses",
SubTitle: "Administrator telah mengaktifkan akses terenkripsi",
diff --git a/app/locales/index.ts b/app/locales/index.ts
index ff7e3a26257..c8eb64df6fa 100644
--- a/app/locales/index.ts
+++ b/app/locales/index.ts
@@ -134,3 +134,34 @@ export function getISOLang() {
const lang = getLang();
return isoLangString[lang] ?? lang;
}
+
+const DEFAULT_STT_LANG = "zh-CN";
+export const STT_LANG_MAP: Record = {
+ 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;
+ }
+}
diff --git a/app/locales/it.ts b/app/locales/it.ts
index 9b0d965d351..59bc1eb1594 100644
--- a/app/locales/it.ts
+++ b/app/locales/it.ts
@@ -1,15 +1,21 @@
import { SubmitKey } from "../store/config";
import type { PartialLocaleType } from "./index";
import { getClientConfig } from "../config/client";
-
+import { SAAS_CHAT_UTM_URL } from "@/app/constant";
const isApp = !!getClientConfig()?.isApp;
const it: PartialLocaleType = {
WIP: "Work in progress...",
Error: {
Unauthorized: isApp
- ? "API Key non valido rilevato. Vai alla pagina [Impostazioni](/#/settings) per controllare se l'API Key è configurata correttamente."
- : "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).",
+ ? `😆 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 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: {
Title: "Password richiesta",
@@ -18,6 +24,11 @@ const it: PartialLocaleType = {
Input: "Inserisci il codice di accesso qui",
Confirm: "Conferma",
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: {
ChatItemCount: (count: number) => `${count} conversazioni`,
@@ -295,6 +306,14 @@ const it: PartialLocaleType = {
},
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: {
Title: "Password di accesso",
SubTitle: "L'amministratore ha abilitato l'accesso criptato",
diff --git a/app/locales/jp.ts b/app/locales/jp.ts
index 1511afc2c5f..e7c81e186bd 100644
--- a/app/locales/jp.ts
+++ b/app/locales/jp.ts
@@ -1,15 +1,21 @@
import { SubmitKey } from "../store/config";
import type { PartialLocaleType } from "./index";
import { getClientConfig } from "../config/client";
-
+import { SAAS_CHAT_UTM_URL } from "@/app/constant";
const isApp = !!getClientConfig()?.isApp;
const jp: PartialLocaleType = {
WIP: "この機能は開発中です",
Error: {
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: {
Title: "パスワードが必要です",
@@ -18,6 +24,10 @@ const jp: PartialLocaleType = {
Input: "ここにアクセスコードを入力",
Confirm: "確認",
Later: "後で",
+ Return: "戻る",
+ SaasTips: "設定が面倒すぎる、すぐに使いたい",
+ TopTips:
+ "🥳 NextChat AIの発売特典で、OpenAI o1、GPT-4o、Claude-3.5などの最新の大規模モデルを今すぐアンロック",
},
ChatItem: {
ChatItemCount: (count: number) => `${count}件の会話`,
@@ -282,6 +292,14 @@ const jp: PartialLocaleType = {
},
Access: {
+ SaasStart: {
+ Title: "NextChat AIを使用する",
+ Label: "(コストパフォーマンスの最も高いソリューション)",
+ SubTitle:
+ "NextChatによって公式に管理されており、設定なしですぐに使用でき、OpenAI o1、GPT-4o、Claude-3.5などの最新の大規模モデルをサポートしています",
+ ChatNow: "今すぐチャット",
+ },
+
AccessCode: {
Title: "アクセスパスワード",
SubTitle: "管理者が暗号化アクセスを有効にしました",
diff --git a/app/locales/ko.ts b/app/locales/ko.ts
index 11e6aa4eb29..f2c433b766f 100644
--- a/app/locales/ko.ts
+++ b/app/locales/ko.ts
@@ -1,15 +1,21 @@
import { SubmitKey } from "../store/config";
import type { PartialLocaleType } from "./index";
import { getClientConfig } from "../config/client";
-
+import { SAAS_CHAT_UTM_URL } from "@/app/constant";
const isApp = !!getClientConfig()?.isApp;
const ko: PartialLocaleType = {
WIP: "곧 출시 예정...",
Error: {
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: {
Title: "비밀번호 필요",
@@ -18,6 +24,10 @@ const ko: PartialLocaleType = {
Input: "여기에 접근 코드를 입력하십시오.",
Confirm: "확인",
Later: "나중에 하기",
+ Return: "돌아가기",
+ SaasTips: "설정이 너무 복잡합니다. 즉시 사용하고 싶습니다.",
+ TopTips:
+ "🥳 NextChat AI 출시 기념 할인, 지금 OpenAI o1, GPT-4o, Claude-3.5 및 최신 대형 모델을 해제하세요",
},
ChatItem: {
ChatItemCount: (count: number) => `${count} 개의 대화`,
@@ -281,6 +291,14 @@ const ko: PartialLocaleType = {
},
Access: {
+ SaasStart: {
+ Title: "NextChat AI 사용하기",
+ Label: "(가장 비용 효율적인 솔루션)",
+ SubTitle:
+ "NextChat에 의해 공식적으로 유지 관리되며, 제로 구성으로 즉시 사용할 수 있으며, OpenAI o1, GPT-4o, Claude-3.5와 같은 최신 대형 모델을 지원합니다",
+ ChatNow: "지금 채팅하기",
+ },
+
AccessCode: {
Title: "접근 비밀번호",
SubTitle: "관리자가 암호화된 접근을 활성화했습니다.",
diff --git a/app/locales/no.ts b/app/locales/no.ts
index 6fc8e86c787..f056ef12fb3 100644
--- a/app/locales/no.ts
+++ b/app/locales/no.ts
@@ -1,15 +1,21 @@
import { SubmitKey } from "../store/config";
import type { PartialLocaleType } from "./index";
import { getClientConfig } from "../config/client";
-
+import { SAAS_CHAT_UTM_URL } from "@/app/constant";
const isApp = !!getClientConfig()?.isApp;
const no: PartialLocaleType = {
WIP: "Arbeid pågår ...",
Error: {
Unauthorized: isApp
- ? "Ugyldig API-nøkkel oppdaget. Gå til [innstillinger](/#/settings) for å sjekke om API-nøkkelen er riktig konfigurert."
- : "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.",
+ ? `😆 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 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: {
Title: "Passord påkrevd",
@@ -18,6 +24,11 @@ const no: PartialLocaleType = {
Input: "Skriv tilgangskoden her",
Confirm: "Bekreft",
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: {
ChatItemCount: (count: number) => `${count} samtaler`,
@@ -288,6 +299,14 @@ const no: PartialLocaleType = {
},
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: {
Title: "Adgangskode",
SubTitle: "Administrator har aktivert kryptert tilgang",
diff --git a/app/locales/pt.ts b/app/locales/pt.ts
index c04081a8b36..152f502284c 100644
--- a/app/locales/pt.ts
+++ b/app/locales/pt.ts
@@ -1,15 +1,21 @@
import { SubmitKey } from "../store/config";
import { PartialLocaleType } from "../locales/index";
import { getClientConfig } from "../config/client";
-
+import { SAAS_CHAT_UTM_URL } from "@/app/constant";
const isApp = !!getClientConfig()?.isApp;
const pt: PartialLocaleType = {
WIP: "Em breve...",
Error: {
Unauthorized: isApp
- ? "Chave API inválida, por favor verifique em [Configurações](/#/settings)."
- : "Acesso não autorizado, por favor insira o código de acesso em [auth](/#/auth) ou insira sua Chave API OpenAI.",
+ ? `😆 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ê 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: {
Title: "Necessário Código de Acesso",
@@ -18,6 +24,10 @@ const pt: PartialLocaleType = {
Input: "código de acesso",
Confirm: "Confirmar",
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: {
ChatItemCount: (count: number) => `${count} mensagens`,
@@ -281,6 +291,14 @@ const pt: PartialLocaleType = {
NoAccess: "Insira a Chave API para verificar o saldo",
},
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: {
Title: "Código de Acesso",
SubTitle: "Controle de Acesso Habilitado",
diff --git a/app/locales/ru.ts b/app/locales/ru.ts
index 0fcf73a249f..4294a3b341b 100644
--- a/app/locales/ru.ts
+++ b/app/locales/ru.ts
@@ -1,15 +1,21 @@
import { SubmitKey } from "../store/config";
import { PartialLocaleType } from "../locales/index";
import { getClientConfig } from "../config/client";
-
+import { SAAS_CHAT_UTM_URL } from "@/app/constant";
const isApp = !!getClientConfig()?.isApp;
const ru: PartialLocaleType = {
WIP: "Скоро...",
Error: {
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: {
Title: "Требуется пароль",
@@ -18,6 +24,10 @@ const ru: PartialLocaleType = {
Input: "Введите код доступа здесь",
Confirm: "Подтвердить",
Later: "Позже",
+ Return: "Назад",
+ SaasTips: "Настройка слишком сложна, я хочу использовать это немедленно",
+ TopTips:
+ "🥳 Предложение по запуску NextChat AI: разблокируйте OpenAI o1, GPT-4o, Claude-3.5 и новейшие большие модели прямо сейчас",
},
ChatItem: {
ChatItemCount: (count: number) => `${count} бесед`,
@@ -286,6 +296,14 @@ const ru: PartialLocaleType = {
},
Access: {
+ SaasStart: {
+ Title: "Используйте NextChat AI",
+ Label: "(Самое экономичное решение)",
+ SubTitle:
+ "Официально поддерживается NextChat, готов к использованию без настройки, поддерживает последние крупные модели, такие как OpenAI o1, GPT-4o и Claude-3.5",
+ ChatNow: "Начать чат",
+ },
+
AccessCode: {
Title: "Пароль доступа",
SubTitle: "Администратор включил защиту паролем",
diff --git a/app/locales/sk.ts b/app/locales/sk.ts
index 8f83c3ba73a..36454de752a 100644
--- a/app/locales/sk.ts
+++ b/app/locales/sk.ts
@@ -1,8 +1,7 @@
import { getClientConfig } from "../config/client";
import { SubmitKey } from "../store/config";
-import { LocaleType } 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
const isApp = !!getClientConfig()?.isApp;
@@ -10,8 +9,14 @@ const sk: PartialLocaleType = {
WIP: "Už čoskoro...",
Error: {
Unauthorized: isApp
- ? "Neplatný API kľúč, prosím skontrolujte ho na stránke [Nastavenia](/#/settings)."
- : "Neoprávnený prístup, prosím zadajte prístupový kód na stránke [auth](/#/auth), alebo zadajte váš OpenAI API kľúč.",
+ ? `😆 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 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: {
Title: "Potrebný prístupový kód",
@@ -20,6 +25,10 @@ const sk: PartialLocaleType = {
Input: "prístupový kód",
Confirm: "Potvrdiť",
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: {
ChatItemCount: (count: number) => `${count} správ`,
@@ -282,6 +291,14 @@ const sk: PartialLocaleType = {
NoAccess: "Zadajte API kľúč na skontrolovanie zostatku",
},
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: {
Title: "Prístupový kód",
SubTitle: "Povolený prístupový kód",
diff --git a/app/locales/tr.ts b/app/locales/tr.ts
index b7f14104750..2082488a51c 100644
--- a/app/locales/tr.ts
+++ b/app/locales/tr.ts
@@ -1,15 +1,21 @@
import { SubmitKey } from "../store/config";
import type { PartialLocaleType } from "./index";
import { getClientConfig } from "../config/client";
-
+import { SAAS_CHAT_UTM_URL } from "@/app/constant";
const isApp = !!getClientConfig()?.isApp;
const tr: PartialLocaleType = {
WIP: "Çalışma devam ediyor...",
Error: {
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."
- : "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.",
+ ? `😆 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️⃣ 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: {
Title: "Şifre Gerekli",
@@ -18,6 +24,10 @@ const tr: PartialLocaleType = {
Input: "Erişim kodunu buraya girin",
Confirm: "Onayla",
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: {
ChatItemCount: (count: number) => `${count} konuşma`,
@@ -286,6 +296,14 @@ const tr: PartialLocaleType = {
},
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: {
Title: "Erişim Şifresi",
SubTitle: "Yönetici şifreli erişimi etkinleştirdi",
diff --git a/app/locales/tw.ts b/app/locales/tw.ts
index b0602a08174..7cb84670894 100644
--- a/app/locales/tw.ts
+++ b/app/locales/tw.ts
@@ -1,14 +1,20 @@
import { getClientConfig } from "../config/client";
import { SubmitKey } from "../store/config";
-
+import { SAAS_CHAT_UTM_URL } from "@/app/constant";
const isApp = !!getClientConfig()?.isApp;
const tw = {
WIP: "此功能仍在開發中……",
Error: {
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: {
@@ -18,6 +24,10 @@ const tw = {
Input: "在此處填寫存取密碼",
Confirm: "確認",
Later: "稍候再說",
+ Return: "返回",
+ SaasTips: "配置太麻煩,想要立即使用",
+ TopTips:
+ "🥳 NextChat AI 首發優惠,立刻解鎖 OpenAI o1, GPT-4o, Claude-3.5 等最新大模型",
},
ChatItem: {
ChatItemCount: (count: number) => `${count} 則對話`,
@@ -287,6 +297,14 @@ const tw = {
},
Access: {
+ SaasStart: {
+ Title: "使用 NextChat AI",
+ Label: "(性價比最高的方案)",
+ SubTitle:
+ "由 NextChat 官方維護,零配置開箱即用,支持 OpenAI o1、GPT-4o、Claude-3.5 等最新大模型",
+ ChatNow: "立刻對話",
+ },
+
AccessCode: {
Title: "存取密碼",
SubTitle: "管理員已開啟加密存取",
diff --git a/app/locales/vi.ts b/app/locales/vi.ts
index 1f20e15a0b1..c53baf35d16 100644
--- a/app/locales/vi.ts
+++ b/app/locales/vi.ts
@@ -1,15 +1,21 @@
import { SubmitKey } from "../store/config";
import type { PartialLocaleType } from "./index";
import { getClientConfig } from "../config/client";
-
+import { SAAS_CHAT_UTM_URL } from "@/app/constant";
const isApp = !!getClientConfig()?.isApp;
const vi: PartialLocaleType = {
WIP: "Sắp ra mắt...",
Error: {
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."
- : "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).",
+ ? `😆 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 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: {
Title: "Cần mật khẩu",
@@ -18,6 +24,10 @@ const vi: PartialLocaleType = {
Input: "Nhập mã truy cập tại đây",
Confirm: "Xác nhận",
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: {
ChatItemCount: (count: number) => `${count} cuộc trò chuyện`,
@@ -283,6 +293,14 @@ const vi: PartialLocaleType = {
},
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: {
Title: "Mật khẩu truy cập",
SubTitle: "Quản trị viên đã bật truy cập mã hóa",
diff --git a/app/masks/index.ts b/app/masks/index.ts
index 92f21c6aea7..bff5c9bbe0a 100644
--- a/app/masks/index.ts
+++ b/app/masks/index.ts
@@ -1,7 +1,4 @@
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";
export { type BuiltinMask } from "./typing";
diff --git a/app/store/access.ts b/app/store/access.ts
index a1014610e39..9fcd227e7c0 100644
--- a/app/store/access.ts
+++ b/app/store/access.ts
@@ -120,6 +120,9 @@ const DEFAULT_ACCESS_STATE = {
disableFastLink: false,
customModels: "",
defaultModel: "",
+
+ // tts config
+ edgeTTSVoiceName: "zh-CN-YunxiNeural",
};
export const useAccessStore = createPersistStore(
@@ -132,6 +135,12 @@ export const useAccessStore = createPersistStore(
return get().needCode;
},
+ edgeVoiceName() {
+ this.fetch();
+
+ return get().edgeTTSVoiceName;
+ },
+
isValidOpenAI() {
return ensure(get(), ["openaiApiKey"]);
},
@@ -202,10 +211,13 @@ export const useAccessStore = createPersistStore(
})
.then((res) => res.json())
.then((res) => {
- // Set default model from env request
- let defaultModel = res.defaultModel ?? "";
- DEFAULT_CONFIG.modelConfig.model =
- defaultModel !== "" ? defaultModel : "gpt-3.5-turbo";
+ const defaultModel = res.defaultModel ?? "";
+ if (defaultModel !== "") {
+ const [model, providerName] = defaultModel.split("@");
+ DEFAULT_CONFIG.modelConfig.model = model;
+ DEFAULT_CONFIG.modelConfig.providerName = providerName;
+ }
+
return res;
})
.then((res: DangerConfig) => {
diff --git a/app/store/chat.ts b/app/store/chat.ts
index 3bcda75389a..968d8cb6422 100644
--- a/app/store/chat.ts
+++ b/app/store/chat.ts
@@ -170,6 +170,28 @@ export const useChatStore = createPersistStore(
}
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() {
set(() => ({
sessions: [createEmptySession()],
@@ -593,6 +615,7 @@ export const useChatStore = createPersistStore(
providerName,
},
onFinish(message) {
+ if (!isValidMessage(message)) return;
get().updateCurrentSession(
(session) =>
(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) {
diff --git a/app/store/config.ts b/app/store/config.ts
index 9985b9e768c..3dcd4d86b80 100644
--- a/app/store/config.ts
+++ b/app/store/config.ts
@@ -5,12 +5,21 @@ import {
DEFAULT_INPUT_TEMPLATE,
DEFAULT_MODELS,
DEFAULT_SIDEBAR_WIDTH,
+ DEFAULT_TTS_ENGINE,
+ DEFAULT_TTS_ENGINES,
+ DEFAULT_TTS_MODEL,
+ DEFAULT_TTS_MODELS,
+ DEFAULT_TTS_VOICE,
+ DEFAULT_TTS_VOICES,
StoreKey,
ServiceProvider,
} from "../constant";
import { createPersistStore } from "../utils/store";
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 {
Enter = "Enter",
@@ -41,6 +50,8 @@ export const DEFAULT_CONFIG = {
enableAutoGenerateTitle: true,
sidebarWidth: DEFAULT_SIDEBAR_WIDTH,
+ enableArtifacts: true, // show artifacts config
+
disablePromptHint: false,
dontShowMaskSplashScreen: false, // dont show splash screen when create chat
@@ -68,11 +79,21 @@ export const DEFAULT_CONFIG = {
quality: "standard" as DalleQuality,
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 ModelConfig = ChatConfig["modelConfig"];
+export type TTSConfig = ChatConfig["ttsConfig"];
export function limitNumber(
x: number,
@@ -87,6 +108,21 @@ export function limitNumber(
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 = {
model(x: string) {
return x as ModelType;
@@ -143,6 +179,21 @@ export const useAppConfig = createPersistStore(
{
name: StoreKey.Config,
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) {
const state = persistedState as ChatConfig;
diff --git a/app/store/plugin.ts b/app/store/plugin.ts
index 2356c6db0a7..40abdc8d9f4 100644
--- a/app/store/plugin.ts
+++ b/app/store/plugin.ts
@@ -1,10 +1,13 @@
import OpenAPIClientAxios from "openapi-client-axios";
-import { getLang, Lang } from "../locales";
import { StoreKey } from "../constant";
import { nanoid } from "nanoid";
import { createPersistStore } from "../utils/store";
+import { getClientConfig } from "../config/client";
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 = {
id: string;
@@ -17,7 +20,6 @@ export type Plugin = {
authLocation?: string;
authHeader?: string;
authToken?: string;
- usingProxy?: boolean;
};
export type FunctionToolItem = {
@@ -47,18 +49,25 @@ export const FunctionToolService = {
plugin?.authType == "basic"
? `Basic ${plugin?.authToken}`
: plugin?.authType == "bearer"
- ? ` Bearer ${plugin?.authToken}`
+ ? `Bearer ${plugin?.authToken}`
: plugin?.authToken;
const authLocation = plugin?.authLocation || "header";
const definition = yaml.load(plugin.content) as any;
const serverURL = definition?.servers?.[0]?.url;
- const baseURL = !!plugin?.usingProxy ? "/api/proxy" : serverURL;
+ const baseURL = !isApp ? "/api/proxy" : serverURL;
const headers: Record = {
- "X-Base-URL": !!plugin?.usingProxy ? serverURL : undefined,
+ "X-Base-URL": !isApp ? serverURL : undefined,
};
if (authLocation == "header") {
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({
definition: yaml.load(plugin.content) as any,
axiosConfigDefaults: {
@@ -107,7 +116,7 @@ export const FunctionToolService = {
return {
type: "function",
function: {
- name: o.operationId,
+ name: getOperationId(o),
description: o.description || o.summary,
parameters: parameters,
},
@@ -115,7 +124,7 @@ export const FunctionToolService = {
}),
funcs: operations.reduce((s, o) => {
// @ts-ignore
- s[o.operationId] = function (args) {
+ s[getOperationId(o)] = function (args) {
const parameters: Record = {};
if (o.parameters instanceof Array) {
o.parameters.forEach((p) => {
@@ -130,8 +139,8 @@ export const FunctionToolService = {
} else if (authLocation == "body") {
args[headerName] = tokenValue;
}
- // @ts-ignore
- return api.client[o.operationId](
+ // @ts-ignore if o.operationId is null, then using o.path and o.method
+ return api.client.paths[o.path][o.method](
parameters,
args,
api.axiosConfigDefaults,
@@ -166,7 +175,7 @@ export const usePluginStore = createPersistStore(
(set, get) => ({
create(plugin?: Partial) {
const plugins = get().plugins;
- const id = nanoid();
+ const id = plugin?.id || nanoid();
plugins[id] = {
...createEmptyPlugin(),
...plugin,
@@ -221,5 +230,42 @@ export const usePluginStore = createPersistStore(
{
name: StoreKey.Plugin,
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;
+ });
+ });
+ });
+ });
+ },
},
);
diff --git a/app/store/prompt.ts b/app/store/prompt.ts
index a25cda5813a..f746edecd4f 100644
--- a/app/store/prompt.ts
+++ b/app/store/prompt.ts
@@ -1,7 +1,7 @@
import Fuse from "fuse.js";
-import { getLang } from "../locales";
-import { StoreKey } from "../constant";
import { nanoid } from "nanoid";
+import { StoreKey } from "../constant";
+import { getLang } from "../locales";
import { createPersistStore } from "../utils/store";
export interface Prompt {
@@ -147,6 +147,11 @@ export const usePromptStore = createPersistStore(
},
onRehydrateStorage(state) {
+ // Skip store rehydration on server side
+ if (typeof window === "undefined") {
+ return;
+ }
+
const PROMPT_URL = "./prompts.json";
type PromptList = Array<[string, string]>;
diff --git a/app/store/sync.ts b/app/store/sync.ts
index d3582e3c935..9db60d5f410 100644
--- a/app/store/sync.ts
+++ b/app/store/sync.ts
@@ -1,5 +1,4 @@
import { getClientConfig } from "../config/client";
-import { Updater } from "../typing";
import { ApiPath, STORAGE_KEY, StoreKey } from "../constant";
import { createPersistStore } from "../utils/store";
import {
@@ -100,15 +99,17 @@ export const useSyncStore = createPersistStore(
const remoteState = await client.get(config.username);
if (!remoteState || remoteState === "") {
await client.set(config.username, JSON.stringify(localState));
- console.log("[Sync] Remote state is empty, using local state instead.");
- return
+ console.log(
+ "[Sync] Remote state is empty, using local state instead.",
+ );
+ return;
} else {
const parsedRemoteState = JSON.parse(
await client.get(config.username),
) as AppState;
mergeAppState(localState, parsedRemoteState);
setLocalAppState(localState);
- }
+ }
} catch (e) {
console.log("[Sync] failed to get remote state", e);
throw e;
diff --git a/app/store/update.ts b/app/store/update.ts
index 7253caffcb9..e68fde369d5 100644
--- a/app/store/update.ts
+++ b/app/store/update.ts
@@ -8,8 +8,6 @@ import { getClientConfig } from "../config/client";
import { createPersistStore } from "../utils/store";
import ChatGptIcon from "../icons/chatgpt.png";
import Locale from "../locales";
-import { use } from "react";
-import { useAppConfig } from ".";
import { ClientApi } from "../client/api";
const ONE_MINUTE = 60 * 1000;
diff --git a/app/utils.ts b/app/utils.ts
index bf745092913..6b2f65952c7 100644
--- a/app/utils.ts
+++ b/app/utils.ts
@@ -3,8 +3,7 @@ import { showToast } from "./components/ui-lib";
import Locale from "./locales";
import { RequestMessage } from "./client/api";
import { ServiceProvider, REQUEST_TIMEOUT_MS } from "./constant";
-import isObject from "lodash-es/isObject";
-import { fetch as tauriFetch, Body, ResponseType } from "@tauri-apps/api/http";
+import { fetch as tauriFetch, ResponseType } from "@tauri-apps/api/http";
export function trimTopic(topic: string) {
// 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("/", "_")}`
+ );
+}
diff --git a/app/utils/audio.ts b/app/utils/audio.ts
new file mode 100644
index 00000000000..f6828c7aac4
--- /dev/null
+++ b/app/utils/audio.ts
@@ -0,0 +1,45 @@
+type TTSPlayer = {
+ init: () => void;
+ play: (audioBuffer: ArrayBuffer, onended: () => void | null) => Promise;
+ 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 };
+}
diff --git a/app/utils/auth-settings-events.ts b/app/utils/auth-settings-events.ts
new file mode 100644
index 00000000000..4f354c5f020
--- /dev/null
+++ b/app/utils/auth-settings-events.ts
@@ -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 });
+}
diff --git a/app/utils/cors.ts b/app/utils/cors.ts
index fa348f9bf5d..f5e5ce6f0a2 100644
--- a/app/utils/cors.ts
+++ b/app/utils/cors.ts
@@ -1,5 +1,5 @@
import { getClientConfig } from "../config/client";
-import { ApiPath, DEFAULT_API_HOST } from "../constant";
+import { DEFAULT_API_HOST } from "../constant";
export function corsPath(path: string) {
const baseUrl = getClientConfig()?.isApp ? `${DEFAULT_API_HOST}` : "";
diff --git a/app/utils/ms_edge_tts.ts b/app/utils/ms_edge_tts.ts
new file mode 100644
index 00000000000..f291ebada93
--- /dev/null
+++ b/app/utils/ms_edge_tts.ts
@@ -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 `
+
+
+ ${input}
+
+
+ `;
+ }
+
+ /**
+ * 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 {
+ // return new Promise((resolve, reject) => {
+ // axios
+ // .get(MsEdgeTTS.VOICES_URL)
+ // .then((res) => resolve(res.data))
+ // .catch(reject);
+ // });
+ // }
+ getVoices(): Promise {
+ 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 {
+ 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 };
+ }
+}
diff --git a/package.json b/package.json
index ca5fcc0f5df..8696f83b558 100644
--- a/package.json
+++ b/package.json
@@ -32,6 +32,7 @@
"idb-keyval": "^6.2.1",
"lodash-es": "^4.17.21",
"mermaid": "^10.6.1",
+ "markdown-to-txt": "^2.0.1",
"nanoid": "^5.0.3",
"next": "^14.1.1",
"node-fetch": "^3.3.1",
@@ -66,6 +67,7 @@
"eslint-config-next": "13.4.19",
"eslint-config-prettier": "^8.8.0",
"eslint-plugin-prettier": "^5.1.3",
+ "eslint-plugin-unused-imports": "^3.2.0",
"husky": "^8.0.0",
"lint-staged": "^13.2.2",
"prettier": "^3.0.2",
@@ -78,4 +80,4 @@
"lint-staged/yaml": "^2.2.2"
},
"packageManager": "yarn@1.22.19"
-}
+}
\ No newline at end of file
diff --git a/public/plugins.json b/public/plugins.json
new file mode 100644
index 00000000000..c4d7ec46a05
--- /dev/null
+++ b/public/plugins.json
@@ -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"
+ }
+]
diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json
index 2a19c933205..eb0d411cba6 100644
--- a/src-tauri/tauri.conf.json
+++ b/src-tauri/tauri.conf.json
@@ -9,7 +9,7 @@
},
"package": {
"productName": "NextChat",
- "version": "2.15.2"
+ "version": "2.15.3"
},
"tauri": {
"allowlist": {
diff --git a/yarn.lock b/yarn.lock
index 4979e4d995e..7e7dd3484f1 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3367,6 +3367,18 @@ eslint-plugin-react@^7.31.7:
semver "^6.3.0"
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:
version "5.1.1"
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"
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:
version "4.6.2"
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
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:
version "4.17.21"
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"
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:
version "5.1.2"
resolved "https://registry.yarnpkg.com/mdast-util-definitions/-/mdast-util-definitions-5.1.2.tgz#9910abb60ac5d7115d6819b57ae0bcef07a3f7a7"