mirror of
https://github.com/Yidadaa/ChatGPT-Next-Web.git
synced 2025-09-01 20:56:59 +08:00
Compare commits
60 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
b5b8593e7f | ||
|
03163d6a61 | ||
|
cada0aa70b | ||
|
75488e4bd5 | ||
|
6da3aab046 | ||
|
ff2589c97f | ||
|
a9f000e7ef | ||
|
d9be63e6cb | ||
|
1a626a68f0 | ||
|
330504b91e | ||
|
6bb0166055 | ||
|
b22988e6b8 | ||
|
f7edac961f | ||
|
5b9b120fa6 | ||
|
f07e4fc87f | ||
|
a0e192b6e4 | ||
|
dc3fa6c780 | ||
|
dd5604f5d9 | ||
|
170936a96e | ||
|
93c9974019 | ||
|
377579e802 | ||
|
c49dbab127 | ||
|
36adfe87fb | ||
|
cdfcf0f068 | ||
|
c3676091ee | ||
|
ec19b86ade | ||
|
7bf74c6a5d | ||
|
cbb50c14e1 | ||
|
d42622a5c1 | ||
|
f9bee1485b | ||
|
c87eee1fda | ||
|
960aa90c32 | ||
|
c4210be3c7 | ||
|
28a49827ff | ||
|
736869454b | ||
|
db9084b0dc | ||
|
c39d75b448 | ||
|
71a546dd44 | ||
|
96e3d3a22c | ||
|
0ad91101a4 | ||
|
e9df58709e | ||
|
a80dcaa1c3 | ||
|
89551774db | ||
|
be58d3afb6 | ||
|
652d803739 | ||
|
2688914125 | ||
|
1d489cfcea | ||
|
cb55ce084c | ||
|
dae7da0e4e | ||
|
e4630e6a0f | ||
|
6d9abf11b8 | ||
|
7e8def50aa | ||
|
2b7f72deec | ||
|
9b1f25140e | ||
|
f4caa0029e | ||
|
15e046b3ce | ||
|
eeef92b068 | ||
|
a1418fe33c | ||
|
6a74d62e98 | ||
|
35ccfb14c2 |
2
.github/workflows/sync.yml
vendored
2
.github/workflows/sync.yml
vendored
@@ -5,7 +5,7 @@ permissions:
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: "0 * * * *" # every hour
|
- cron: "0 0 * * *" # every day
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
17
README.md
17
README.md
@@ -31,7 +31,7 @@ One-Click to deploy well-designed ChatGPT web UI on Vercel.
|
|||||||
- New in v2: create, share and debug your chat tools with prompt templates (mask)
|
- New in v2: create, share and debug your chat tools with prompt templates (mask)
|
||||||
- Awesome prompts powered by [awesome-chatgpt-prompts-zh](https://github.com/PlexPt/awesome-chatgpt-prompts-zh) and [awesome-chatgpt-prompts](https://github.com/f/awesome-chatgpt-prompts)
|
- Awesome prompts powered by [awesome-chatgpt-prompts-zh](https://github.com/PlexPt/awesome-chatgpt-prompts-zh) and [awesome-chatgpt-prompts](https://github.com/f/awesome-chatgpt-prompts)
|
||||||
- Automatically compresses chat history to support long conversations while also saving your tokens
|
- Automatically compresses chat history to support long conversations while also saving your tokens
|
||||||
- I18n: English, 简体中文, 繁体中文, 日本語, Español, Italiano, Türkçe, Deutsch
|
- I18n: English, 简体中文, 繁体中文, 日本語, Español, Italiano, Türkçe, Deutsch, Tiếng Việt, Русский, Čeština
|
||||||
|
|
||||||
## Roadmap
|
## Roadmap
|
||||||
|
|
||||||
@@ -62,7 +62,7 @@ One-Click to deploy well-designed ChatGPT web UI on Vercel.
|
|||||||
- 预制角色功能(面具),方便地创建、分享和调试你的个性化对话
|
- 预制角色功能(面具),方便地创建、分享和调试你的个性化对话
|
||||||
- 海量的内置 prompt 列表,来自[中文](https://github.com/PlexPt/awesome-chatgpt-prompts-zh)和[英文](https://github.com/f/awesome-chatgpt-prompts)
|
- 海量的内置 prompt 列表,来自[中文](https://github.com/PlexPt/awesome-chatgpt-prompts-zh)和[英文](https://github.com/f/awesome-chatgpt-prompts)
|
||||||
- 自动压缩上下文聊天记录,在节省 Token 的同时支持超长对话
|
- 自动压缩上下文聊天记录,在节省 Token 的同时支持超长对话
|
||||||
- 多国语言支持:English, 简体中文, 繁体中文, 日本語, Español, Italiano, Türkçe, Deutsch
|
- 多国语言支持:English, 简体中文, 繁体中文, 日本語, Español, Italiano, Türkçe, Deutsch, Tiếng Việt, Русский, Čeština
|
||||||
- 拥有自己的域名?好上加好,绑定后即可在任何地方**无障碍**快速访问
|
- 拥有自己的域名?好上加好,绑定后即可在任何地方**无障碍**快速访问
|
||||||
|
|
||||||
## 开发计划
|
## 开发计划
|
||||||
@@ -83,6 +83,7 @@ One-Click to deploy well-designed ChatGPT web UI on Vercel.
|
|||||||
## 最新动态
|
## 最新动态
|
||||||
|
|
||||||
- 🚀 v2.0 已经发布,现在你可以使用面具功能快速创建预制对话了! 了解更多: [ChatGPT 提示词高阶技能:零次、一次和少样本提示](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/138)。
|
- 🚀 v2.0 已经发布,现在你可以使用面具功能快速创建预制对话了! 了解更多: [ChatGPT 提示词高阶技能:零次、一次和少样本提示](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/138)。
|
||||||
|
- 💡 想要更方便地随时随地使用本项目?可以试下这款桌面插件:https://github.com/mushan0x0/AI0x0.com
|
||||||
|
|
||||||
## Get Started
|
## Get Started
|
||||||
|
|
||||||
@@ -167,7 +168,13 @@ Specify OpenAI organization ID.
|
|||||||
|
|
||||||
> Default: Empty
|
> Default: Empty
|
||||||
|
|
||||||
If you do not want users to input their own API key, set this environment variable to 1.
|
If you do not want users to input their own API key, set this value to 1.
|
||||||
|
|
||||||
|
### `DISABLE_GPT4` (optional)
|
||||||
|
|
||||||
|
> Default: Empty
|
||||||
|
|
||||||
|
If you do not want users to use GPT-4, set this value to 1.
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
@@ -255,6 +262,10 @@ bash <(curl -s https://raw.githubusercontent.com/Yidadaa/ChatGPT-Next-Web/main/s
|
|||||||
[@WingCH](https://github.com/WingCH)
|
[@WingCH](https://github.com/WingCH)
|
||||||
[@jtung4](https://github.com/jtung4)
|
[@jtung4](https://github.com/jtung4)
|
||||||
[@micozhu](https://github.com/micozhu)
|
[@micozhu](https://github.com/micozhu)
|
||||||
|
[@jhansion](https://github.com/jhansion)
|
||||||
|
[@Sha1rholder](https://github.com/Sha1rholder)
|
||||||
|
[@AnsonHyq](https://github.com/AnsonHyq)
|
||||||
|
[@synwith](https://github.com/synwith)
|
||||||
|
|
||||||
### Contributor
|
### Contributor
|
||||||
|
|
||||||
|
@@ -64,7 +64,7 @@ code1,code2,code3
|
|||||||
|
|
||||||
## 环境变量
|
## 环境变量
|
||||||
|
|
||||||
> 本项目大多数配置项都通过环境变量来设置。
|
> 本项目大多数配置项都通过环境变量来设置,教程:[如何修改 Vercel 环境变量](./docs/vercel-cn.md)。
|
||||||
|
|
||||||
### `OPENAI_API_KEY` (必填项)
|
### `OPENAI_API_KEY` (必填项)
|
||||||
|
|
||||||
@@ -94,6 +94,10 @@ OpenAI 接口代理 URL,如果你手动配置了 openai 接口代理,请填
|
|||||||
|
|
||||||
如果你不想让用户自行填入 API Key,将此环境变量设置为 1 即可。
|
如果你不想让用户自行填入 API Key,将此环境变量设置为 1 即可。
|
||||||
|
|
||||||
|
### `DISABLE_GPT4` (可选)
|
||||||
|
|
||||||
|
如果你不想让用户使用 GPT-4,将此环境变量设置为 1 即可。
|
||||||
|
|
||||||
## 开发
|
## 开发
|
||||||
|
|
||||||
> 强烈不建议在本地进行开发或者部署,由于一些技术原因,很难在本地配置好 OpenAI API 代理,除非你能保证可以直连 OpenAI 服务器。
|
> 强烈不建议在本地进行开发或者部署,由于一些技术原因,很难在本地配置好 OpenAI API 代理,除非你能保证可以直连 OpenAI 服务器。
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { NextRequest, NextResponse } from "next/server";
|
import { NextResponse } from "next/server";
|
||||||
|
|
||||||
import { getServerSideConfig } from "../../config/server";
|
import { getServerSideConfig } from "../../config/server";
|
||||||
|
|
||||||
@@ -9,6 +9,7 @@ const serverConfig = getServerSideConfig();
|
|||||||
const DANGER_CONFIG = {
|
const DANGER_CONFIG = {
|
||||||
needCode: serverConfig.needCode,
|
needCode: serverConfig.needCode,
|
||||||
hideUserApiKey: serverConfig.hideUserApiKey,
|
hideUserApiKey: serverConfig.hideUserApiKey,
|
||||||
|
enableGPT4: serverConfig.enableGPT4,
|
||||||
};
|
};
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
@@ -16,6 +16,7 @@ import { Link, 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";
|
||||||
|
import { useRef, useEffect } from "react";
|
||||||
|
|
||||||
export function ChatItem(props: {
|
export function ChatItem(props: {
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
@@ -29,6 +30,14 @@ export function ChatItem(props: {
|
|||||||
narrow?: boolean;
|
narrow?: boolean;
|
||||||
mask: Mask;
|
mask: Mask;
|
||||||
}) {
|
}) {
|
||||||
|
const draggableRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
useEffect(() => {
|
||||||
|
if (props.selected && draggableRef.current) {
|
||||||
|
draggableRef.current?.scrollIntoView({
|
||||||
|
block: "center",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [props.selected]);
|
||||||
return (
|
return (
|
||||||
<Draggable draggableId={`${props.id}`} index={props.index}>
|
<Draggable draggableId={`${props.id}`} index={props.index}>
|
||||||
{(provided) => (
|
{(provided) => (
|
||||||
@@ -37,7 +46,10 @@ export function ChatItem(props: {
|
|||||||
props.selected && styles["chat-item-selected"]
|
props.selected && styles["chat-item-selected"]
|
||||||
}`}
|
}`}
|
||||||
onClick={props.onClick}
|
onClick={props.onClick}
|
||||||
ref={provided.innerRef}
|
ref={(ele) => {
|
||||||
|
draggableRef.current = ele;
|
||||||
|
provided.innerRef(ele);
|
||||||
|
}}
|
||||||
{...provided.draggableProps}
|
{...provided.draggableProps}
|
||||||
{...provided.dragHandleProps}
|
{...provided.dragHandleProps}
|
||||||
title={`${props.title}\n${Locale.ChatItem.ChatItemCount(
|
title={`${props.title}\n${Locale.ChatItem.ChatItemCount(
|
||||||
|
@@ -53,7 +53,7 @@ import chatStyle from "./chat.module.scss";
|
|||||||
|
|
||||||
import { ListItem, Modal, showModal } from "./ui-lib";
|
import { ListItem, Modal, showModal } from "./ui-lib";
|
||||||
import { useLocation, useNavigate } from "react-router-dom";
|
import { useLocation, useNavigate } from "react-router-dom";
|
||||||
import { Path } from "../constant";
|
import { LAST_INPUT_KEY, Path } from "../constant";
|
||||||
import { Avatar } from "./emoji";
|
import { Avatar } from "./emoji";
|
||||||
import { MaskAvatar, MaskConfig } from "./mask";
|
import { MaskAvatar, MaskConfig } from "./mask";
|
||||||
import { useMaskStore } from "../store/mask";
|
import { useMaskStore } from "../store/mask";
|
||||||
@@ -230,7 +230,9 @@ export function PromptHints(props: {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const onKeyDown = (e: KeyboardEvent) => {
|
const onKeyDown = (e: KeyboardEvent) => {
|
||||||
if (noPrompts) return;
|
if (noPrompts) return;
|
||||||
|
if (e.metaKey || e.altKey || e.ctrlKey) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
// arrow up / down to select prompt
|
// arrow up / down to select prompt
|
||||||
const changeIndex = (delta: number) => {
|
const changeIndex = (delta: number) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
@@ -404,7 +406,6 @@ export function Chat() {
|
|||||||
|
|
||||||
const inputRef = useRef<HTMLTextAreaElement>(null);
|
const inputRef = useRef<HTMLTextAreaElement>(null);
|
||||||
const [userInput, setUserInput] = useState("");
|
const [userInput, setUserInput] = useState("");
|
||||||
const [beforeInput, setBeforeInput] = useState("");
|
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const { submitKey, shouldSubmit } = useSubmitHandler();
|
const { submitKey, shouldSubmit } = useSubmitHandler();
|
||||||
const { scrollRef, setAutoScroll, scrollToBottom } = useScrollToBottom();
|
const { scrollRef, setAutoScroll, scrollToBottom } = useScrollToBottom();
|
||||||
@@ -477,7 +478,7 @@ export function Chat() {
|
|||||||
if (userInput.trim() === "") return;
|
if (userInput.trim() === "") return;
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
chatStore.onUserInput(userInput).then(() => setIsLoading(false));
|
chatStore.onUserInput(userInput).then(() => setIsLoading(false));
|
||||||
setBeforeInput(userInput);
|
localStorage.setItem(LAST_INPUT_KEY, userInput);
|
||||||
setUserInput("");
|
setUserInput("");
|
||||||
setPromptHints([]);
|
setPromptHints([]);
|
||||||
if (!isMobileScreen) inputRef.current?.focus();
|
if (!isMobileScreen) inputRef.current?.focus();
|
||||||
@@ -491,9 +492,13 @@ export function Chat() {
|
|||||||
|
|
||||||
// check if should send message
|
// check if should send message
|
||||||
const onInputKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
const onInputKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||||
// if ArrowUp and no userInput
|
// if ArrowUp and no userInput, fill with last input
|
||||||
if (e.key === "ArrowUp" && userInput.length <= 0) {
|
if (
|
||||||
setUserInput(beforeInput);
|
e.key === "ArrowUp" &&
|
||||||
|
userInput.length <= 0 &&
|
||||||
|
!(e.metaKey || e.altKey || e.ctrlKey)
|
||||||
|
) {
|
||||||
|
setUserInput(localStorage.getItem(LAST_INPUT_KEY) ?? "");
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -503,11 +508,6 @@ export function Chat() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
const onRightClick = (e: any, message: Message) => {
|
const onRightClick = (e: any, message: Message) => {
|
||||||
// auto fill user input
|
|
||||||
if (message.role === "user") {
|
|
||||||
setUserInput(message.content);
|
|
||||||
}
|
|
||||||
|
|
||||||
// copy to clipboard
|
// copy to clipboard
|
||||||
if (selectOrCopy(e.currentTarget, message.content)) {
|
if (selectOrCopy(e.currentTarget, message.content)) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -795,7 +795,14 @@ export function Chat() {
|
|||||||
scrollToBottom={scrollToBottom}
|
scrollToBottom={scrollToBottom}
|
||||||
hitBottom={hitBottom}
|
hitBottom={hitBottom}
|
||||||
showPromptHints={() => {
|
showPromptHints={() => {
|
||||||
|
// Click again to close
|
||||||
|
if (promptHints.length > 0) {
|
||||||
|
setPromptHints([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
inputRef.current?.focus();
|
inputRef.current?.focus();
|
||||||
|
setUserInput("/");
|
||||||
onSearch("");
|
onSearch("");
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@@ -186,7 +186,7 @@
|
|||||||
.chat-item-delete {
|
.chat-item-delete {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 10px;
|
top: 10px;
|
||||||
right: -20px;
|
right: 0;
|
||||||
transition: all ease 0.3s;
|
transition: all ease 0.3s;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@@ -194,7 +194,7 @@
|
|||||||
|
|
||||||
.chat-item:hover > .chat-item-delete {
|
.chat-item:hover > .chat-item-delete {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
right: 10px;
|
transform: translateX(-10px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-item:hover > .chat-item-delete:hover {
|
.chat-item:hover > .chat-item-delete:hover {
|
||||||
|
@@ -23,7 +23,6 @@ import {
|
|||||||
} from "react-router-dom";
|
} from "react-router-dom";
|
||||||
import { SideBar } from "./sidebar";
|
import { SideBar } from "./sidebar";
|
||||||
import { useAppConfig } from "../store/config";
|
import { useAppConfig } from "../store/config";
|
||||||
import { useMaskStore } from "../store/mask";
|
|
||||||
|
|
||||||
export function Loading(props: { noLogo?: boolean }) {
|
export function Loading(props: { noLogo?: boolean }) {
|
||||||
return (
|
return (
|
||||||
@@ -64,17 +63,17 @@ export function useSwitchTheme() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const metaDescriptionDark = document.querySelector(
|
const metaDescriptionDark = document.querySelector(
|
||||||
'meta[name="theme-color"][media]',
|
'meta[name="theme-color"][media*="dark"]',
|
||||||
);
|
);
|
||||||
const metaDescriptionLight = document.querySelector(
|
const metaDescriptionLight = document.querySelector(
|
||||||
'meta[name="theme-color"]:not([media])',
|
'meta[name="theme-color"][media*="light"]',
|
||||||
);
|
);
|
||||||
|
|
||||||
if (config.theme === "auto") {
|
if (config.theme === "auto") {
|
||||||
metaDescriptionDark?.setAttribute("content", "#151515");
|
metaDescriptionDark?.setAttribute("content", "#151515");
|
||||||
metaDescriptionLight?.setAttribute("content", "#fafafa");
|
metaDescriptionLight?.setAttribute("content", "#fafafa");
|
||||||
} else {
|
} else {
|
||||||
const themeColor = getCSSVar("--themeColor");
|
const themeColor = getCSSVar("--theme-color");
|
||||||
metaDescriptionDark?.setAttribute("content", themeColor);
|
metaDescriptionDark?.setAttribute("content", themeColor);
|
||||||
metaDescriptionLight?.setAttribute("content", themeColor);
|
metaDescriptionLight?.setAttribute("content", themeColor);
|
||||||
}
|
}
|
||||||
@@ -91,12 +90,24 @@ const useHasHydrated = () => {
|
|||||||
return hasHydrated;
|
return hasHydrated;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const loadAsyncGoogleFont = () => {
|
||||||
|
const linkEl = document.createElement("link");
|
||||||
|
linkEl.rel = "stylesheet";
|
||||||
|
linkEl.href =
|
||||||
|
"/google-fonts/css2?family=Noto+Sans+SC:wght@300;400;700;900&display=swap";
|
||||||
|
document.head.appendChild(linkEl);
|
||||||
|
};
|
||||||
|
|
||||||
function Screen() {
|
function Screen() {
|
||||||
const config = useAppConfig();
|
const config = useAppConfig();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const isHome = location.pathname === Path.Home;
|
const isHome = location.pathname === Path.Home;
|
||||||
const isMobileScreen = useMobileScreen();
|
const isMobileScreen = useMobileScreen();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadAsyncGoogleFont();
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={
|
className={
|
||||||
|
@@ -14,7 +14,7 @@ import CopyIcon from "../icons/copy.svg";
|
|||||||
|
|
||||||
import { DEFAULT_MASK_AVATAR, Mask, useMaskStore } from "../store/mask";
|
import { DEFAULT_MASK_AVATAR, Mask, useMaskStore } from "../store/mask";
|
||||||
import { Message, ModelConfig, ROLES, useChatStore } from "../store";
|
import { Message, ModelConfig, ROLES, useChatStore } from "../store";
|
||||||
import { Input, List, ListItem, Modal, Popover, showToast } from "./ui-lib";
|
import { Input, List, ListItem, Modal, Popover, Select } from "./ui-lib";
|
||||||
import { Avatar, AvatarPicker } from "./emoji";
|
import { Avatar, AvatarPicker } from "./emoji";
|
||||||
import Locale, { AllLangs, Lang } from "../locales";
|
import Locale, { AllLangs, Lang } from "../locales";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
@@ -116,7 +116,7 @@ function ContextPromptItem(props: {
|
|||||||
return (
|
return (
|
||||||
<div className={chatStyle["context-prompt-row"]}>
|
<div className={chatStyle["context-prompt-row"]}>
|
||||||
{!focusingInput && (
|
{!focusingInput && (
|
||||||
<select
|
<Select
|
||||||
value={props.prompt.role}
|
value={props.prompt.role}
|
||||||
className={chatStyle["context-role"]}
|
className={chatStyle["context-role"]}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
@@ -131,7 +131,7 @@ function ContextPromptItem(props: {
|
|||||||
{r}
|
{r}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</Select>
|
||||||
)}
|
)}
|
||||||
<Input
|
<Input
|
||||||
value={props.prompt.content}
|
value={props.prompt.content}
|
||||||
@@ -307,7 +307,7 @@ export function MaskPage() {
|
|||||||
autoFocus
|
autoFocus
|
||||||
onInput={(e) => onSearch(e.currentTarget.value)}
|
onInput={(e) => onSearch(e.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
<select
|
<Select
|
||||||
className={styles["mask-filter-lang"]}
|
className={styles["mask-filter-lang"]}
|
||||||
value={filterLang ?? Locale.Settings.Lang.All}
|
value={filterLang ?? Locale.Settings.Lang.All}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
@@ -327,7 +327,7 @@ export function MaskPage() {
|
|||||||
{Locale.Settings.Lang.Options[lang]}
|
{Locale.Settings.Lang.Options[lang]}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</Select>
|
||||||
|
|
||||||
<IconButton
|
<IconButton
|
||||||
className={styles["mask-create"]}
|
className={styles["mask-create"]}
|
||||||
|
@@ -2,7 +2,7 @@ import { ALL_MODELS, ModalConfigValidator, ModelConfig } from "../store";
|
|||||||
|
|
||||||
import Locale from "../locales";
|
import Locale from "../locales";
|
||||||
import { InputRange } from "./input-range";
|
import { InputRange } from "./input-range";
|
||||||
import { List, ListItem } from "./ui-lib";
|
import { List, ListItem, Select } from "./ui-lib";
|
||||||
|
|
||||||
export function ModelConfigList(props: {
|
export function ModelConfigList(props: {
|
||||||
modelConfig: ModelConfig;
|
modelConfig: ModelConfig;
|
||||||
@@ -11,7 +11,7 @@ export function ModelConfigList(props: {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ListItem title={Locale.Settings.Model}>
|
<ListItem title={Locale.Settings.Model}>
|
||||||
<select
|
<Select
|
||||||
value={props.modelConfig.model}
|
value={props.modelConfig.model}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
props.updateConfig(
|
props.updateConfig(
|
||||||
@@ -27,7 +27,7 @@ export function ModelConfigList(props: {
|
|||||||
{v.name}
|
{v.name}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</Select>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
<ListItem
|
<ListItem
|
||||||
title={Locale.Settings.Temperature.Title}
|
title={Locale.Settings.Temperature.Title}
|
||||||
|
@@ -10,7 +10,15 @@ 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 EyeIcon from "../icons/eye.svg";
|
import EyeIcon from "../icons/eye.svg";
|
||||||
import { Input, List, ListItem, Modal, PasswordInput, Popover } from "./ui-lib";
|
import {
|
||||||
|
Input,
|
||||||
|
List,
|
||||||
|
ListItem,
|
||||||
|
Modal,
|
||||||
|
PasswordInput,
|
||||||
|
Popover,
|
||||||
|
Select,
|
||||||
|
} from "./ui-lib";
|
||||||
import { ModelConfigList } from "./model-config";
|
import { ModelConfigList } from "./model-config";
|
||||||
|
|
||||||
import { IconButton } from "./button";
|
import { IconButton } from "./button";
|
||||||
@@ -368,7 +376,7 @@ export function Settings() {
|
|||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
<ListItem title={Locale.Settings.SendKey}>
|
<ListItem title={Locale.Settings.SendKey}>
|
||||||
<select
|
<Select
|
||||||
value={config.submitKey}
|
value={config.submitKey}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
updateConfig(
|
updateConfig(
|
||||||
@@ -382,11 +390,11 @@ export function Settings() {
|
|||||||
{v}
|
{v}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</Select>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
<ListItem title={Locale.Settings.Theme}>
|
<ListItem title={Locale.Settings.Theme}>
|
||||||
<select
|
<Select
|
||||||
value={config.theme}
|
value={config.theme}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
updateConfig(
|
updateConfig(
|
||||||
@@ -399,11 +407,11 @@ export function Settings() {
|
|||||||
{v}
|
{v}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</Select>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
<ListItem title={Locale.Settings.Lang.Name}>
|
<ListItem title={Locale.Settings.Lang.Name}>
|
||||||
<select
|
<Select
|
||||||
value={getLang()}
|
value={getLang()}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
changeLang(e.target.value as any);
|
changeLang(e.target.value as any);
|
||||||
@@ -414,7 +422,7 @@ export function Settings() {
|
|||||||
{Locale.Settings.Lang.Options[lang]}
|
{Locale.Settings.Lang.Options[lang]}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</Select>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
<ListItem
|
<ListItem
|
||||||
@@ -565,9 +573,9 @@ export function Settings() {
|
|||||||
<List>
|
<List>
|
||||||
<ModelConfigList
|
<ModelConfigList
|
||||||
modelConfig={config.modelConfig}
|
modelConfig={config.modelConfig}
|
||||||
updateConfig={(upater) => {
|
updateConfig={(updater) => {
|
||||||
const modelConfig = { ...config.modelConfig };
|
const modelConfig = { ...config.modelConfig };
|
||||||
upater(modelConfig);
|
updater(modelConfig);
|
||||||
config.update((config) => (config.modelConfig = modelConfig));
|
config.update((config) => (config.modelConfig = modelConfig));
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@@ -32,6 +32,28 @@ const ChatList = dynamic(async () => (await import("./chat-list")).ChatList, {
|
|||||||
loading: () => null,
|
loading: () => null,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function useHotKey() {
|
||||||
|
const chatStore = useChatStore();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const onKeyDown = (e: KeyboardEvent) => {
|
||||||
|
if (e.metaKey || e.altKey || e.ctrlKey) {
|
||||||
|
const n = chatStore.sessions.length;
|
||||||
|
const limit = (x: number) => (x + n) % n;
|
||||||
|
const i = chatStore.currentSessionIndex;
|
||||||
|
if (e.key === "ArrowUp") {
|
||||||
|
chatStore.selectSession(limit(i - 1));
|
||||||
|
} else if (e.key === "ArrowDown") {
|
||||||
|
chatStore.selectSession(limit(i + 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener("keydown", onKeyDown);
|
||||||
|
return () => window.removeEventListener("keydown", onKeyDown);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function useDragSideBar() {
|
function useDragSideBar() {
|
||||||
const limit = (x: number) => Math.min(MAX_SIDEBAR_WIDTH, x);
|
const limit = (x: number) => Math.min(MAX_SIDEBAR_WIDTH, x);
|
||||||
|
|
||||||
@@ -86,9 +108,10 @@ export function SideBar(props: { className?: string }) {
|
|||||||
// drag side bar
|
// drag side bar
|
||||||
const { onDragMouseDown, shouldNarrow } = useDragSideBar();
|
const { onDragMouseDown, shouldNarrow } = useDragSideBar();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const config = useAppConfig();
|
const config = useAppConfig();
|
||||||
|
|
||||||
|
useHotKey();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`${styles.sidebar} ${props.className} ${
|
className={`${styles.sidebar} ${props.className} ${
|
||||||
|
@@ -203,3 +203,28 @@
|
|||||||
resize: none;
|
resize: none;
|
||||||
min-width: 50px;
|
min-width: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.select-with-icon {
|
||||||
|
position: relative;
|
||||||
|
max-width: fit-content;
|
||||||
|
|
||||||
|
.select-with-icon-select {
|
||||||
|
height: 100%;
|
||||||
|
border: var(--border-in-light);
|
||||||
|
padding: 10px 25px 10px 10px;
|
||||||
|
border-radius: 10px;
|
||||||
|
appearance: none;
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: var(--white);
|
||||||
|
color: var(--black);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-with-icon-icon {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
right: 10px;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
@@ -3,6 +3,7 @@ import LoadingIcon from "../icons/three-dots.svg";
|
|||||||
import CloseIcon from "../icons/close.svg";
|
import CloseIcon from "../icons/close.svg";
|
||||||
import EyeIcon from "../icons/eye.svg";
|
import EyeIcon from "../icons/eye.svg";
|
||||||
import EyeOffIcon from "../icons/eye-off.svg";
|
import EyeOffIcon from "../icons/eye-off.svg";
|
||||||
|
import DownIcon from "../icons/down.svg";
|
||||||
|
|
||||||
import { createRoot } from "react-dom/client";
|
import { createRoot } from "react-dom/client";
|
||||||
import React, { HTMLProps, useEffect, useState } from "react";
|
import React, { HTMLProps, useEffect, useState } from "react";
|
||||||
@@ -244,3 +245,20 @@ export function PasswordInput(props: HTMLProps<HTMLInputElement>) {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function Select(
|
||||||
|
props: React.DetailedHTMLProps<
|
||||||
|
React.SelectHTMLAttributes<HTMLSelectElement>,
|
||||||
|
HTMLSelectElement
|
||||||
|
>,
|
||||||
|
) {
|
||||||
|
const { className, children, ...otherProps } = props;
|
||||||
|
return (
|
||||||
|
<div className={`${styles["select-with-icon"]} ${className}`}>
|
||||||
|
<select className={styles["select-with-icon-select"]} {...otherProps}>
|
||||||
|
{children}
|
||||||
|
</select>
|
||||||
|
<DownIcon className={styles["select-with-icon-icon"]} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@@ -8,6 +8,7 @@ declare global {
|
|||||||
PROXY_URL?: string;
|
PROXY_URL?: string;
|
||||||
VERCEL?: string;
|
VERCEL?: string;
|
||||||
HIDE_USER_API_KEY?: string; // disable user's api key input
|
HIDE_USER_API_KEY?: string; // disable user's api key input
|
||||||
|
DISABLE_GPT4?: string; // allow user to use gpt-4 or not
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -40,5 +41,6 @@ export const getServerSideConfig = () => {
|
|||||||
proxyUrl: process.env.PROXY_URL,
|
proxyUrl: process.env.PROXY_URL,
|
||||||
isVercel: !!process.env.VERCEL,
|
isVercel: !!process.env.VERCEL,
|
||||||
hideUserApiKey: !!process.env.HIDE_USER_API_KEY,
|
hideUserApiKey: !!process.env.HIDE_USER_API_KEY,
|
||||||
|
enableGPT4: !process.env.DISABLE_GPT4,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@@ -38,3 +38,5 @@ export const MIN_SIDEBAR_WIDTH = 230;
|
|||||||
export const NARROW_SIDEBAR_WIDTH = 100;
|
export const NARROW_SIDEBAR_WIDTH = 100;
|
||||||
|
|
||||||
export const ACCESS_CODE_PREFIX = "ak-";
|
export const ACCESS_CODE_PREFIX = "ak-";
|
||||||
|
|
||||||
|
export const LAST_INPUT_KEY = "last-input";
|
||||||
|
1
app/icons/down.svg
Normal file
1
app/icons/down.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16" fill="none"><defs><rect id="path_0" x="0" y="0" width="16" height="16" /></defs><g opacity="1" transform="translate(0 0) rotate(-90 8 8)"><mask id="bg-mask-0" fill="white"><use xlink:href="#path_0"></use></mask><g mask="url(#bg-mask-0)" ><path id="路径 1" style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0" transform="translate(6.333333333333333 4) rotate(0 2 4)" d="M4,8L0,4L4,0 " /></g></g></svg>
|
After Width: | Height: | Size: 575 B |
@@ -9,11 +9,19 @@ const buildConfig = getBuildConfig();
|
|||||||
export const metadata = {
|
export const metadata = {
|
||||||
title: "ChatGPT Next Web",
|
title: "ChatGPT Next Web",
|
||||||
description: "Your personal ChatGPT Chat Bot.",
|
description: "Your personal ChatGPT Chat Bot.",
|
||||||
|
viewport: {
|
||||||
|
width: "device-width",
|
||||||
|
initialScale: 1,
|
||||||
|
maximumScale: 1,
|
||||||
|
},
|
||||||
|
themeColor: [
|
||||||
|
{ media: "(prefers-color-scheme: light)", color: "#fafafa" },
|
||||||
|
{ media: "(prefers-color-scheme: dark)", color: "#151515" },
|
||||||
|
],
|
||||||
appleWebApp: {
|
appleWebApp: {
|
||||||
title: "ChatGPT Next Web",
|
title: "ChatGPT Next Web",
|
||||||
statusBarStyle: "default",
|
statusBarStyle: "default",
|
||||||
},
|
},
|
||||||
themeColor: "#fafafa",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function RootLayout({
|
export default function RootLayout({
|
||||||
@@ -24,22 +32,8 @@ export default function RootLayout({
|
|||||||
return (
|
return (
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta
|
|
||||||
name="viewport"
|
|
||||||
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"
|
|
||||||
/>
|
|
||||||
<meta
|
|
||||||
name="theme-color"
|
|
||||||
content="#151515"
|
|
||||||
media="(prefers-color-scheme: dark)"
|
|
||||||
/>
|
|
||||||
<meta name="version" content={buildConfig.commitId} />
|
<meta name="version" content={buildConfig.commitId} />
|
||||||
<link rel="manifest" href="/site.webmanifest"></link>
|
<link rel="manifest" href="/site.webmanifest"></link>
|
||||||
<link rel="preconnect" href="https://fonts.proxy.ustclug.org"></link>
|
|
||||||
<link
|
|
||||||
href="https://fonts.proxy.ustclug.org/css2?family=Noto+Sans+SC:wght@300;400;700;900&display=swap"
|
|
||||||
rel="stylesheet"
|
|
||||||
></link>
|
|
||||||
<script src="/serviceWorkerRegister.js" defer></script>
|
<script src="/serviceWorkerRegister.js" defer></script>
|
||||||
</head>
|
</head>
|
||||||
<body>{children}</body>
|
<body>{children}</body>
|
||||||
|
@@ -67,7 +67,7 @@ const cn = {
|
|||||||
ConfirmClearAll: "确认清除所有数据?",
|
ConfirmClearAll: "确认清除所有数据?",
|
||||||
},
|
},
|
||||||
Lang: {
|
Lang: {
|
||||||
Name: "Language",
|
Name: "Language", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language`
|
||||||
All: "所有语言",
|
All: "所有语言",
|
||||||
Options: {
|
Options: {
|
||||||
cn: "简体中文",
|
cn: "简体中文",
|
||||||
@@ -78,7 +78,9 @@ const cn = {
|
|||||||
tr: "Türkçe",
|
tr: "Türkçe",
|
||||||
jp: "日本語",
|
jp: "日本語",
|
||||||
de: "Deutsch",
|
de: "Deutsch",
|
||||||
vi: "Vietnamese",
|
vi: "Tiếng Việt",
|
||||||
|
ru: "Русский",
|
||||||
|
cs: "Čeština",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Avatar: "头像",
|
Avatar: "头像",
|
||||||
|
244
app/locales/cs.ts
Normal file
244
app/locales/cs.ts
Normal file
@@ -0,0 +1,244 @@
|
|||||||
|
import { SubmitKey } from "../store/config";
|
||||||
|
import type { LocaleType } from "./index";
|
||||||
|
|
||||||
|
const cs: LocaleType = {
|
||||||
|
WIP: "V přípravě...",
|
||||||
|
Error: {
|
||||||
|
Unauthorized:
|
||||||
|
"Neoprávněný přístup, zadejte přístupový kód na stránce nastavení.",
|
||||||
|
},
|
||||||
|
ChatItem: {
|
||||||
|
ChatItemCount: (count: number) => `${count} zpráv`,
|
||||||
|
},
|
||||||
|
Chat: {
|
||||||
|
SubTitle: (count: number) => `${count} zpráv s ChatGPT`,
|
||||||
|
Actions: {
|
||||||
|
ChatList: "Přejít na seznam chatů",
|
||||||
|
CompressedHistory: "Pokyn z komprimované paměti historie",
|
||||||
|
Export: "Exportovat všechny zprávy jako Markdown",
|
||||||
|
Copy: "Kopírovat",
|
||||||
|
Stop: "Zastavit",
|
||||||
|
Retry: "Zopakovat",
|
||||||
|
Delete: "Smazat",
|
||||||
|
},
|
||||||
|
Rename: "Přejmenovat chat",
|
||||||
|
Typing: "Píše...",
|
||||||
|
Input: (submitKey: string) => {
|
||||||
|
var inputHints = `${submitKey} pro odeslání`;
|
||||||
|
if (submitKey === String(SubmitKey.Enter)) {
|
||||||
|
inputHints += ", Shift + Enter pro řádkování";
|
||||||
|
}
|
||||||
|
return inputHints + ", / pro vyhledávání pokynů";
|
||||||
|
},
|
||||||
|
Send: "Odeslat",
|
||||||
|
Config: {
|
||||||
|
Reset: "Obnovit výchozí",
|
||||||
|
SaveAs: "Uložit jako Masku",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Export: {
|
||||||
|
Title: "Všechny zprávy",
|
||||||
|
Copy: "Kopírovat vše",
|
||||||
|
Download: "Stáhnout",
|
||||||
|
MessageFromYou: "Zpráva od vás",
|
||||||
|
MessageFromChatGPT: "Zpráva z ChatGPT",
|
||||||
|
},
|
||||||
|
Memory: {
|
||||||
|
Title: "Pokyn z paměti",
|
||||||
|
EmptyContent: "Zatím nic.",
|
||||||
|
Send: "Odeslat paměť",
|
||||||
|
Copy: "Kopírovat paměť",
|
||||||
|
Reset: "Obnovit relaci",
|
||||||
|
ResetConfirm:
|
||||||
|
"Resetováním se vymaže historie aktuálních konverzací i paměť historie pokynů. Opravdu chcete provést obnovu?",
|
||||||
|
},
|
||||||
|
Home: {
|
||||||
|
NewChat: "Nový chat",
|
||||||
|
DeleteChat: "Potvrzujete smazání vybrané konverzace?",
|
||||||
|
DeleteToast: "Chat smazán",
|
||||||
|
Revert: "Zvrátit",
|
||||||
|
},
|
||||||
|
Settings: {
|
||||||
|
Title: "Nastavení",
|
||||||
|
SubTitle: "Všechna nastavení",
|
||||||
|
Actions: {
|
||||||
|
ClearAll: "Vymazat všechna data",
|
||||||
|
ResetAll: "Obnovit veškeré nastavení",
|
||||||
|
Close: "Zavřít",
|
||||||
|
ConfirmResetAll: "Jste si jisti, že chcete obnovit všechna nastavení?",
|
||||||
|
ConfirmClearAll: "Jste si jisti, že chcete smazat všechna data?",
|
||||||
|
},
|
||||||
|
Lang: {
|
||||||
|
Name: "Language", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language`
|
||||||
|
All: "Všechny jazyky",
|
||||||
|
Options: {
|
||||||
|
cn: "简体中文",
|
||||||
|
en: "English",
|
||||||
|
tw: "繁體中文",
|
||||||
|
es: "Español",
|
||||||
|
it: "Italiano",
|
||||||
|
tr: "Türkçe",
|
||||||
|
jp: "日本語",
|
||||||
|
de: "Deutsch",
|
||||||
|
vi: "Tiếng Việt",
|
||||||
|
ru: "Русский",
|
||||||
|
cs: "Čeština",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Avatar: "Avatar",
|
||||||
|
FontSize: {
|
||||||
|
Title: "Velikost písma",
|
||||||
|
SubTitle: "Nastavení velikosti písma obsahu chatu",
|
||||||
|
},
|
||||||
|
Update: {
|
||||||
|
Version: (x: string) => `Verze: ${x}`,
|
||||||
|
IsLatest: "Aktuální verze",
|
||||||
|
CheckUpdate: "Zkontrolovat aktualizace",
|
||||||
|
IsChecking: "Kontrola aktualizace...",
|
||||||
|
FoundUpdate: (x: string) => `Nalezena nová verze: ${x}`,
|
||||||
|
GoToUpdate: "Aktualizovat",
|
||||||
|
},
|
||||||
|
SendKey: "Odeslat klíč",
|
||||||
|
Theme: "Téma",
|
||||||
|
TightBorder: "Těsné ohraničení",
|
||||||
|
SendPreviewBubble: {
|
||||||
|
Title: "Odesílat chatovací bublinu s náhledem",
|
||||||
|
SubTitle: "Zobrazit v náhledu bubliny",
|
||||||
|
},
|
||||||
|
Mask: {
|
||||||
|
Title: "Úvodní obrazovka Masek",
|
||||||
|
SubTitle: "Před zahájením nového chatu zobrazte úvodní obrazovku Masek",
|
||||||
|
},
|
||||||
|
Prompt: {
|
||||||
|
Disable: {
|
||||||
|
Title: "Deaktivovat automatické dokončování",
|
||||||
|
SubTitle: "Zadejte / pro spuštění automatického dokončování",
|
||||||
|
},
|
||||||
|
List: "Seznam pokynů",
|
||||||
|
ListCount: (builtin: number, custom: number) =>
|
||||||
|
`${builtin} vestavěných, ${custom} uživatelských`,
|
||||||
|
Edit: "Upravit",
|
||||||
|
Modal: {
|
||||||
|
Title: "Seznam pokynů",
|
||||||
|
Add: "Přidat pokyn",
|
||||||
|
Search: "Hledat pokyny",
|
||||||
|
},
|
||||||
|
EditModal: {
|
||||||
|
Title: "Editovat pokyn",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
HistoryCount: {
|
||||||
|
Title: "Počet připojených zpráv",
|
||||||
|
SubTitle: "Počet odeslaných připojených zpráv na žádost",
|
||||||
|
},
|
||||||
|
CompressThreshold: {
|
||||||
|
Title: "Práh pro kompresi historie",
|
||||||
|
SubTitle:
|
||||||
|
"Komprese proběhne, pokud délka nekomprimovaných zpráv přesáhne tuto hodnotu",
|
||||||
|
},
|
||||||
|
Token: {
|
||||||
|
Title: "API klíč",
|
||||||
|
SubTitle: "Použitím klíče ignorujete omezení přístupového kódu",
|
||||||
|
Placeholder: "Klíč API OpenAI",
|
||||||
|
},
|
||||||
|
Usage: {
|
||||||
|
Title: "Stav účtu",
|
||||||
|
SubTitle(used: any, total: any) {
|
||||||
|
return `Použito tento měsíc $${used}, předplaceno $${total}`;
|
||||||
|
},
|
||||||
|
IsChecking: "Kontroluji...",
|
||||||
|
Check: "Zkontrolovat",
|
||||||
|
NoAccess: "Pro kontrolu zůstatku zadejte klíč API",
|
||||||
|
},
|
||||||
|
AccessCode: {
|
||||||
|
Title: "Přístupový kód",
|
||||||
|
SubTitle: "Kontrola přístupu povolena",
|
||||||
|
Placeholder: "Potřebujete přístupový kód",
|
||||||
|
},
|
||||||
|
Model: "Model",
|
||||||
|
Temperature: {
|
||||||
|
Title: "Teplota",
|
||||||
|
SubTitle: "Větší hodnota činí výstup náhodnějším",
|
||||||
|
},
|
||||||
|
MaxTokens: {
|
||||||
|
Title: "Max. počet tokenů",
|
||||||
|
SubTitle: "Maximální délka vstupního tokenu a generovaných tokenů",
|
||||||
|
},
|
||||||
|
PresencePenlty: {
|
||||||
|
Title: "Přítomnostní korekce",
|
||||||
|
SubTitle: "Větší hodnota zvyšuje pravděpodobnost nových témat.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Store: {
|
||||||
|
DefaultTopic: "Nová konverzace",
|
||||||
|
BotHello: "Ahoj! Jak mohu dnes pomoci?",
|
||||||
|
Error: "Něco se pokazilo, zkuste to prosím později.",
|
||||||
|
Prompt: {
|
||||||
|
History: (content: string) =>
|
||||||
|
"Toto je shrnutí historie chatu mezi umělou inteligencí a uživatelem v podobě rekapitulace: " +
|
||||||
|
content,
|
||||||
|
Topic:
|
||||||
|
"Vytvořte prosím název o čtyřech až pěti slovech vystihující průběh našeho rozhovoru bez jakýchkoli úvodních slov, interpunkčních znamének, uvozovek, teček, symbolů nebo dalšího textu. Odstraňte uvozovky.",
|
||||||
|
Summarize:
|
||||||
|
"Krátce shrň naši diskusi v rozsahu do 200 slov a použij ji jako podnět pro budoucí kontext.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Copy: {
|
||||||
|
Success: "Zkopírováno do schránky",
|
||||||
|
Failed: "Kopírování selhalo, prosím, povolte přístup ke schránce",
|
||||||
|
},
|
||||||
|
Context: {
|
||||||
|
Toast: (x: any) => `Použití ${x} kontextových pokynů`,
|
||||||
|
Edit: "Kontextové a paměťové pokyny",
|
||||||
|
Add: "Přidat pokyn",
|
||||||
|
},
|
||||||
|
Plugin: {
|
||||||
|
Name: "Plugin",
|
||||||
|
},
|
||||||
|
Mask: {
|
||||||
|
Name: "Maska",
|
||||||
|
Page: {
|
||||||
|
Title: "Šablona pokynu",
|
||||||
|
SubTitle: (count: number) => `${count} šablon pokynů`,
|
||||||
|
Search: "Hledat v šablonách",
|
||||||
|
Create: "Vytvořit",
|
||||||
|
},
|
||||||
|
Item: {
|
||||||
|
Info: (count: number) => `${count} pokynů`,
|
||||||
|
Chat: "Chat",
|
||||||
|
View: "Zobrazit",
|
||||||
|
Edit: "Upravit",
|
||||||
|
Delete: "Smazat",
|
||||||
|
DeleteConfirm: "Potvrdit smazání?",
|
||||||
|
},
|
||||||
|
EditModal: {
|
||||||
|
Title: (readonly: boolean) =>
|
||||||
|
`Editovat šablonu pokynu ${readonly ? "(pouze ke čtení)" : ""}`,
|
||||||
|
Download: "Stáhnout",
|
||||||
|
Clone: "Duplikovat",
|
||||||
|
},
|
||||||
|
Config: {
|
||||||
|
Avatar: "Avatar Bota",
|
||||||
|
Name: "Jméno Bota",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
NewChat: {
|
||||||
|
Return: "Zpět",
|
||||||
|
Skip: "Přeskočit",
|
||||||
|
Title: "Vyberte Masku",
|
||||||
|
SubTitle: "Chatovat s duší za Maskou",
|
||||||
|
More: "Najít více",
|
||||||
|
NotShow: "Nezobrazovat znovu",
|
||||||
|
ConfirmNoShow: "Potvrdit zakázání?Můžete jej povolit později v nastavení.",
|
||||||
|
},
|
||||||
|
|
||||||
|
UI: {
|
||||||
|
Confirm: "Potvrdit",
|
||||||
|
Cancel: "Zrušit",
|
||||||
|
Close: "Zavřít",
|
||||||
|
Create: "Vytvořit",
|
||||||
|
Edit: "Upravit",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default cs;
|
@@ -71,7 +71,7 @@ const de: LocaleType = {
|
|||||||
},
|
},
|
||||||
Lang: {
|
Lang: {
|
||||||
Name: "Language", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language`
|
Name: "Language", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language`
|
||||||
All: "All Languages",
|
All: "Alle Sprachen",
|
||||||
Options: {
|
Options: {
|
||||||
cn: "简体中文",
|
cn: "简体中文",
|
||||||
en: "English",
|
en: "English",
|
||||||
@@ -81,7 +81,9 @@ const de: LocaleType = {
|
|||||||
tr: "Türkçe",
|
tr: "Türkçe",
|
||||||
jp: "日本語",
|
jp: "日本語",
|
||||||
de: "Deutsch",
|
de: "Deutsch",
|
||||||
vi: "Vietnamese",
|
vi: "Tiếng Việt",
|
||||||
|
ru: "Русский",
|
||||||
|
cs: "Čeština",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Avatar: "Avatar",
|
Avatar: "Avatar",
|
||||||
|
@@ -80,7 +80,9 @@ const en: LocaleType = {
|
|||||||
tr: "Türkçe",
|
tr: "Türkçe",
|
||||||
jp: "日本語",
|
jp: "日本語",
|
||||||
de: "Deutsch",
|
de: "Deutsch",
|
||||||
vi: "Vietnamese",
|
vi: "Tiếng Việt",
|
||||||
|
ru: "Русский",
|
||||||
|
cs: "Čeština",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Avatar: "Avatar",
|
Avatar: "Avatar",
|
||||||
|
@@ -69,18 +69,20 @@ const es: LocaleType = {
|
|||||||
ConfirmClearAll: "Are you sure you want to reset all chat?",
|
ConfirmClearAll: "Are you sure you want to reset all chat?",
|
||||||
},
|
},
|
||||||
Lang: {
|
Lang: {
|
||||||
Name: "Language",
|
Name: "Language", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language`
|
||||||
All: "All Languages",
|
All: "Todos los idiomas",
|
||||||
Options: {
|
Options: {
|
||||||
cn: "简体中文",
|
cn: "简体中文",
|
||||||
en: "Inglés",
|
en: "English",
|
||||||
tw: "繁體中文",
|
tw: "繁體中文",
|
||||||
es: "Español",
|
es: "Español",
|
||||||
it: "Italiano",
|
it: "Italiano",
|
||||||
tr: "Türkçe",
|
tr: "Türkçe",
|
||||||
jp: "日本語",
|
jp: "日本語",
|
||||||
de: "Deutsch",
|
de: "Deutsch",
|
||||||
vi: "Vietnamese",
|
vi: "Tiếng Việt",
|
||||||
|
ru: "Русский",
|
||||||
|
cs: "Čeština",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Avatar: "Avatar",
|
Avatar: "Avatar",
|
||||||
|
@@ -7,6 +7,8 @@ import TR from "./tr";
|
|||||||
import JP from "./jp";
|
import JP from "./jp";
|
||||||
import DE from "./de";
|
import DE from "./de";
|
||||||
import VI from "./vi";
|
import VI from "./vi";
|
||||||
|
import RU from "./ru";
|
||||||
|
import CS from "./cs";
|
||||||
|
|
||||||
export type { LocaleType } from "./cn";
|
export type { LocaleType } from "./cn";
|
||||||
|
|
||||||
@@ -20,6 +22,8 @@ export const AllLangs = [
|
|||||||
"jp",
|
"jp",
|
||||||
"de",
|
"de",
|
||||||
"vi",
|
"vi",
|
||||||
|
"ru",
|
||||||
|
"cs",
|
||||||
] as const;
|
] as const;
|
||||||
export type Lang = (typeof AllLangs)[number];
|
export type Lang = (typeof AllLangs)[number];
|
||||||
|
|
||||||
@@ -82,4 +86,6 @@ export default {
|
|||||||
jp: JP,
|
jp: JP,
|
||||||
de: DE,
|
de: DE,
|
||||||
vi: VI,
|
vi: VI,
|
||||||
|
ru: RU,
|
||||||
|
cs: CS,
|
||||||
}[getLang()] as typeof CN;
|
}[getLang()] as typeof CN;
|
||||||
|
@@ -69,8 +69,8 @@ const it: LocaleType = {
|
|||||||
ConfirmClearAll: "Sei sicuro vuoi cancellare tutte le chat?",
|
ConfirmClearAll: "Sei sicuro vuoi cancellare tutte le chat?",
|
||||||
},
|
},
|
||||||
Lang: {
|
Lang: {
|
||||||
Name: "Lingue",
|
Name: "Language", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language`
|
||||||
All: "All Languages",
|
All: "Tutte le lingue",
|
||||||
Options: {
|
Options: {
|
||||||
cn: "简体中文",
|
cn: "简体中文",
|
||||||
en: "English",
|
en: "English",
|
||||||
@@ -80,7 +80,9 @@ const it: LocaleType = {
|
|||||||
tr: "Türkçe",
|
tr: "Türkçe",
|
||||||
jp: "日本語",
|
jp: "日本語",
|
||||||
de: "Deutsch",
|
de: "Deutsch",
|
||||||
vi: "Vietnamese",
|
vi: "Tiếng Việt",
|
||||||
|
ru: "Русский",
|
||||||
|
cs: "Čeština",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Avatar: "Avatar",
|
Avatar: "Avatar",
|
||||||
|
@@ -69,7 +69,7 @@ const jp: LocaleType = {
|
|||||||
ConfirmClearAll: "すべてのチャットをリセットしてもよろしいですか?",
|
ConfirmClearAll: "すべてのチャットをリセットしてもよろしいですか?",
|
||||||
},
|
},
|
||||||
Lang: {
|
Lang: {
|
||||||
Name: "Language",
|
Name: "Language", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language`
|
||||||
All: "所有语言",
|
All: "所有语言",
|
||||||
Options: {
|
Options: {
|
||||||
cn: "简体中文",
|
cn: "简体中文",
|
||||||
@@ -80,7 +80,9 @@ const jp: LocaleType = {
|
|||||||
tr: "Türkçe",
|
tr: "Türkçe",
|
||||||
jp: "日本語",
|
jp: "日本語",
|
||||||
de: "Deutsch",
|
de: "Deutsch",
|
||||||
vi: "Vietnamese",
|
vi: "Tiếng Việt",
|
||||||
|
ru: "Русский",
|
||||||
|
cs: "Čeština",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Avatar: "アバター",
|
Avatar: "アバター",
|
||||||
|
250
app/locales/ru.ts
Normal file
250
app/locales/ru.ts
Normal file
@@ -0,0 +1,250 @@
|
|||||||
|
import { SubmitKey } from "../store/config";
|
||||||
|
import type { LocaleType } from "./index";
|
||||||
|
|
||||||
|
const ru: LocaleType = {
|
||||||
|
WIP: "Скоро...",
|
||||||
|
Error: {
|
||||||
|
Unauthorized:
|
||||||
|
"Несанкционированный доступ. Пожалуйста, введите код доступа на странице настроек.",
|
||||||
|
},
|
||||||
|
ChatItem: {
|
||||||
|
ChatItemCount: (count: number) => `${count} сообщений`,
|
||||||
|
},
|
||||||
|
Chat: {
|
||||||
|
SubTitle: (count: number) => `${count} сообщений с ChatGPT`,
|
||||||
|
Actions: {
|
||||||
|
ChatList: "Перейти к списку чатов",
|
||||||
|
CompressedHistory: "Сжатая история памяти",
|
||||||
|
Export: "Экспортировать все сообщения в формате Markdown",
|
||||||
|
Copy: "Копировать",
|
||||||
|
Stop: "Остановить",
|
||||||
|
Retry: "Повторить",
|
||||||
|
Delete: "Удалить",
|
||||||
|
},
|
||||||
|
Rename: "Переименовать чат",
|
||||||
|
Typing: "Печатает…",
|
||||||
|
Input: (submitKey: string) => {
|
||||||
|
var inputHints = `${submitKey} для отправки сообщения`;
|
||||||
|
if (submitKey === String(SubmitKey.Enter)) {
|
||||||
|
inputHints += ", Shift + Enter для переноса строки";
|
||||||
|
}
|
||||||
|
return inputHints + ", / для поиска подсказок";
|
||||||
|
},
|
||||||
|
Send: "Отправить",
|
||||||
|
Config: {
|
||||||
|
Reset: "Сбросить настройки",
|
||||||
|
SaveAs: "Сохранить как маску",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Export: {
|
||||||
|
Title: "Все сообщения",
|
||||||
|
Copy: "Копировать все",
|
||||||
|
Download: "Скачать",
|
||||||
|
MessageFromYou: "Сообщение от вас",
|
||||||
|
MessageFromChatGPT: "Сообщение от ChatGPT",
|
||||||
|
},
|
||||||
|
Memory: {
|
||||||
|
Title: "Память",
|
||||||
|
EmptyContent: "Пусто.",
|
||||||
|
Send: "Отправить память",
|
||||||
|
Copy: "Копировать память",
|
||||||
|
Reset: "Сбросить сессию",
|
||||||
|
ResetConfirm:
|
||||||
|
"При сбросе текущая история переписки и историческая память будут удалены. Вы уверены, что хотите сбросить?",
|
||||||
|
},
|
||||||
|
Home: {
|
||||||
|
NewChat: "Новый чат",
|
||||||
|
DeleteChat: "Вы действительно хотите удалить выбранный разговор?",
|
||||||
|
DeleteToast: "Чат удален",
|
||||||
|
Revert: "Отмена",
|
||||||
|
},
|
||||||
|
Settings: {
|
||||||
|
Title: "Настройки",
|
||||||
|
SubTitle: "Все настройки",
|
||||||
|
Actions: {
|
||||||
|
ClearAll: "Очистить все данные",
|
||||||
|
ResetAll: "Сбросить все настройки",
|
||||||
|
Close: "Закрыть",
|
||||||
|
ConfirmResetAll: "Вы уверены, что хотите сбросить все настройки?",
|
||||||
|
ConfirmClearAll: "Вы уверены, что хотите очистить все данные?",
|
||||||
|
},
|
||||||
|
Lang: {
|
||||||
|
Name: "Language", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language`
|
||||||
|
All: "Все языки",
|
||||||
|
Options: {
|
||||||
|
cn: "简体中文",
|
||||||
|
en: "English",
|
||||||
|
tw: "繁體中文",
|
||||||
|
es: "Español",
|
||||||
|
it: "Italiano",
|
||||||
|
tr: "Türkçe",
|
||||||
|
jp: "日本語",
|
||||||
|
de: "Deutsch",
|
||||||
|
vi: "Tiếng Việt",
|
||||||
|
ru: "Русский",
|
||||||
|
cs: "Čeština",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Avatar: "Аватар",
|
||||||
|
FontSize: {
|
||||||
|
Title: "Размер шрифта",
|
||||||
|
SubTitle: "Настроить размер шрифта контента чата",
|
||||||
|
},
|
||||||
|
Update: {
|
||||||
|
Version: (x: string) => `Версия: ${x}`,
|
||||||
|
IsLatest: "Последняя версия",
|
||||||
|
CheckUpdate: "Проверить обновление",
|
||||||
|
IsChecking: "Проверка обновления...",
|
||||||
|
FoundUpdate: (x: string) => `Найдена новая версия: ${x}`,
|
||||||
|
GoToUpdate: "Обновить",
|
||||||
|
},
|
||||||
|
SendKey: "Клавиша отправки",
|
||||||
|
Theme: "Тема",
|
||||||
|
TightBorder: "Узкая граница",
|
||||||
|
SendPreviewBubble: {
|
||||||
|
Title: "Отправить предпросмотр",
|
||||||
|
SubTitle: "Предварительный просмотр markdown в пузыре",
|
||||||
|
},
|
||||||
|
Mask: {
|
||||||
|
Title: "Экран заставки маски",
|
||||||
|
SubTitle: "Показывать экран заставки маски перед началом нового чата",
|
||||||
|
},
|
||||||
|
Prompt: {
|
||||||
|
Disable: {
|
||||||
|
Title: "Отключить автозаполнение",
|
||||||
|
SubTitle: "Ввод / для запуска автозаполнения",
|
||||||
|
},
|
||||||
|
List: "Список подсказок",
|
||||||
|
ListCount: (builtin: number, custom: number) =>
|
||||||
|
`${builtin} встроенных, ${custom} пользовательских`,
|
||||||
|
Edit: "Редактировать",
|
||||||
|
Modal: {
|
||||||
|
Title: "Список подсказок",
|
||||||
|
Add: "Добавить",
|
||||||
|
Search: "Поиск подсказок",
|
||||||
|
},
|
||||||
|
EditModal: {
|
||||||
|
Title: "Редактировать подсказку",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
HistoryCount: {
|
||||||
|
Title: "Количество прикрепляемых сообщений",
|
||||||
|
SubTitle:
|
||||||
|
"Количество отправляемых сообщений, прикрепляемых к каждому запросу",
|
||||||
|
},
|
||||||
|
CompressThreshold: {
|
||||||
|
Title: "Порог сжатия истории",
|
||||||
|
SubTitle:
|
||||||
|
"Будет сжимать, если длина несжатых сообщений превышает указанное значение",
|
||||||
|
},
|
||||||
|
Token: {
|
||||||
|
Title: "API ключ",
|
||||||
|
SubTitle: "Используйте свой ключ, чтобы игнорировать лимит доступа",
|
||||||
|
Placeholder: "API ключ OpenAI",
|
||||||
|
},
|
||||||
|
Usage: {
|
||||||
|
Title: "Баланс аккаунта",
|
||||||
|
SubTitle(used: any, total: any) {
|
||||||
|
return `Использовано в этом месяце $${used}, подписка $${total}`;
|
||||||
|
},
|
||||||
|
IsChecking: "Проверка...",
|
||||||
|
Check: "Проверить",
|
||||||
|
NoAccess: "Введите API ключ, чтобы проверить баланс",
|
||||||
|
},
|
||||||
|
AccessCode: {
|
||||||
|
Title: "Код доступа",
|
||||||
|
SubTitle: "Контроль доступа включен",
|
||||||
|
Placeholder: "Требуется код доступа",
|
||||||
|
},
|
||||||
|
Model: "Модель",
|
||||||
|
Temperature: {
|
||||||
|
Title: "Температура",
|
||||||
|
SubTitle: "Чем выше значение, тем более случайный вывод",
|
||||||
|
},
|
||||||
|
MaxTokens: {
|
||||||
|
Title: "Максимальное количество токенов",
|
||||||
|
SubTitle: "Максимальная длина вводных и генерируемых токенов",
|
||||||
|
},
|
||||||
|
PresencePenlty: {
|
||||||
|
Title: "Штраф за повторения",
|
||||||
|
SubTitle:
|
||||||
|
"Чем выше значение, тем больше вероятность общения на новые темы",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Store: {
|
||||||
|
DefaultTopic: "Новый разговор",
|
||||||
|
BotHello: "Здравствуйте! Как я могу вам помочь сегодня?",
|
||||||
|
Error: "Что-то пошло не так. Пожалуйста, попробуйте еще раз позже.",
|
||||||
|
Prompt: {
|
||||||
|
History: (content: string) =>
|
||||||
|
"Это краткое содержание истории чата между ИИ и пользователем: " +
|
||||||
|
content,
|
||||||
|
Topic:
|
||||||
|
"Пожалуйста, создайте заголовок из четырех или пяти слов, который кратко описывает нашу беседу, без введения, знаков пунктуации, кавычек, точек, символов или дополнительного текста. Удалите кавычки.",
|
||||||
|
Summarize:
|
||||||
|
"Кратко изложите нашу дискуссию в 200 словах или менее для использования в будущем контексте.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Copy: {
|
||||||
|
Success: "Скопировано в буфер обмена",
|
||||||
|
Failed:
|
||||||
|
"Не удалось скопировать, пожалуйста, предоставьте разрешение на доступ к буферу обмена",
|
||||||
|
},
|
||||||
|
Context: {
|
||||||
|
Toast: (x: any) => `С ${x} контекстными подсказками`,
|
||||||
|
Edit: "Контекстные и памятные подсказки",
|
||||||
|
Add: "Добавить подсказку",
|
||||||
|
},
|
||||||
|
Plugin: {
|
||||||
|
Name: "Плагин",
|
||||||
|
},
|
||||||
|
Mask: {
|
||||||
|
Name: "Маска",
|
||||||
|
Page: {
|
||||||
|
Title: "Шаблон подсказки",
|
||||||
|
SubTitle: (count: number) => `${count} шаблонов подсказок`,
|
||||||
|
Search: "Поиск шаблонов",
|
||||||
|
Create: "Создать",
|
||||||
|
},
|
||||||
|
Item: {
|
||||||
|
Info: (count: number) => `${count} подсказок`,
|
||||||
|
Chat: "Чат",
|
||||||
|
View: "Просмотр",
|
||||||
|
Edit: "Редактировать",
|
||||||
|
Delete: "Удалить",
|
||||||
|
DeleteConfirm: "Подтвердить удаление?",
|
||||||
|
},
|
||||||
|
EditModal: {
|
||||||
|
Title: (readonly: boolean) =>
|
||||||
|
`Редактирование шаблона подсказки ${
|
||||||
|
readonly ? "(только для чтения)" : ""
|
||||||
|
}`,
|
||||||
|
Download: "Скачать",
|
||||||
|
Clone: "Клонировать",
|
||||||
|
},
|
||||||
|
Config: {
|
||||||
|
Avatar: "Аватар бота",
|
||||||
|
Name: "Имя бота",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
NewChat: {
|
||||||
|
Return: "Вернуться",
|
||||||
|
Skip: "Пропустить",
|
||||||
|
Title: "Выберите маску",
|
||||||
|
SubTitle: "Общайтесь с душой за маской",
|
||||||
|
More: "Найти еще",
|
||||||
|
NotShow: "Не показывать снова",
|
||||||
|
ConfirmNoShow:
|
||||||
|
"Подтвердите отключение? Вы можете включить это позже в настройках.",
|
||||||
|
},
|
||||||
|
|
||||||
|
UI: {
|
||||||
|
Confirm: "Подтвердить",
|
||||||
|
Cancel: "Отмена",
|
||||||
|
Close: "Закрыть",
|
||||||
|
Create: "Создать",
|
||||||
|
Edit: "Редактировать",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ru;
|
@@ -70,7 +70,7 @@ const tr: LocaleType = {
|
|||||||
},
|
},
|
||||||
Lang: {
|
Lang: {
|
||||||
Name: "Language", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language`
|
Name: "Language", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language`
|
||||||
All: "All Languages",
|
All: "Tüm Diller",
|
||||||
Options: {
|
Options: {
|
||||||
cn: "简体中文",
|
cn: "简体中文",
|
||||||
en: "English",
|
en: "English",
|
||||||
@@ -80,7 +80,9 @@ const tr: LocaleType = {
|
|||||||
tr: "Türkçe",
|
tr: "Türkçe",
|
||||||
jp: "日本語",
|
jp: "日本語",
|
||||||
de: "Deutsch",
|
de: "Deutsch",
|
||||||
vi: "Vietnamese",
|
vi: "Tiếng Việt",
|
||||||
|
ru: "Русский",
|
||||||
|
cs: "Čeština",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Avatar: "Avatar",
|
Avatar: "Avatar",
|
||||||
|
@@ -67,7 +67,7 @@ const tw: LocaleType = {
|
|||||||
ConfirmClearAll: "您確定要清除所有数据嗎?",
|
ConfirmClearAll: "您確定要清除所有数据嗎?",
|
||||||
},
|
},
|
||||||
Lang: {
|
Lang: {
|
||||||
Name: "Language",
|
Name: "Language", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language`
|
||||||
All: "所有语言",
|
All: "所有语言",
|
||||||
Options: {
|
Options: {
|
||||||
cn: "简体中文",
|
cn: "简体中文",
|
||||||
@@ -78,7 +78,9 @@ const tw: LocaleType = {
|
|||||||
tr: "Türkçe",
|
tr: "Türkçe",
|
||||||
jp: "日本語",
|
jp: "日本語",
|
||||||
de: "Deutsch",
|
de: "Deutsch",
|
||||||
vi: "Vietnamese",
|
vi: "Tiếng Việt",
|
||||||
|
ru: "Русский",
|
||||||
|
cs: "Čeština",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Avatar: "大頭貼",
|
Avatar: "大頭貼",
|
||||||
|
@@ -2,7 +2,7 @@ import { SubmitKey } from "../store/config";
|
|||||||
import type { LocaleType } from "./index";
|
import type { LocaleType } from "./index";
|
||||||
|
|
||||||
const vi: LocaleType = {
|
const vi: LocaleType = {
|
||||||
WIP: "Coming Soon...",
|
WIP: "Sắp ra mắt...",
|
||||||
Error: {
|
Error: {
|
||||||
Unauthorized:
|
Unauthorized:
|
||||||
"Truy cập chưa xác thực, vui lòng nhập mã truy cập trong trang cài đặt.",
|
"Truy cập chưa xác thực, vui lòng nhập mã truy cập trong trang cài đặt.",
|
||||||
@@ -80,7 +80,9 @@ const vi: LocaleType = {
|
|||||||
tr: "Türkçe",
|
tr: "Türkçe",
|
||||||
jp: "日本語",
|
jp: "日本語",
|
||||||
de: "Deutsch",
|
de: "Deutsch",
|
||||||
vi: "Vietnamese",
|
vi: "Tiếng Việt",
|
||||||
|
ru: "Русский",
|
||||||
|
cs: "Čeština",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Avatar: "Ảnh đại diện",
|
Avatar: "Ảnh đại diện",
|
||||||
|
@@ -1,6 +1,38 @@
|
|||||||
import { BuiltinMask } from "./typing";
|
import { BuiltinMask } from "./typing";
|
||||||
|
|
||||||
export const CN_MASKS: BuiltinMask[] = [
|
export const CN_MASKS: BuiltinMask[] = [{
|
||||||
|
avatar:"gpt-bot",
|
||||||
|
name:"文生图",
|
||||||
|
context: [
|
||||||
|
{role:"system",
|
||||||
|
content:"You are a useful Assistant that won't refuse to draw picture, instead, you will output specific markdown code for picture description, but don't use code block.",
|
||||||
|
date:"",
|
||||||
|
},
|
||||||
|
{role:"user",
|
||||||
|
content:"Can you draw some pictures for me?",
|
||||||
|
date:"",
|
||||||
|
},
|
||||||
|
{role:"assistant",
|
||||||
|
content:"Sure, what do you want me to draw?",
|
||||||
|
date:"",
|
||||||
|
},
|
||||||
|
{role:"system",
|
||||||
|
content:"助手善于判断用户意图,当确定需要提供图片时,助手会变得沉默寡言,只使用以下格式输出markdown图片:,因为这个语法可以自动按照提示生成并渲染图片。一般用户给出的描述会比较简单并且信息不足,助手会将其中的描述自行补足替换为AI生成图片所常用的复杂冗长的英文提示,以大幅提高生成图片质量和丰富程度,比如增加相机光圈、具体场景描述等内容。助手会避免用代码块或原始块包围markdown标记,因为那样只会渲染出代码块或原始块而不是图片。",
|
||||||
|
date:"",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
modelConfig:{
|
||||||
|
model:"gpt-3.5-turbo",
|
||||||
|
temperature:1,
|
||||||
|
max_tokens:2000,
|
||||||
|
presence_penalty:0,
|
||||||
|
sendMemory:true,
|
||||||
|
historyMessageCount:32,
|
||||||
|
compressMessageLengthThreshold:1000,
|
||||||
|
},
|
||||||
|
lang:"cn",
|
||||||
|
builtin:true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
avatar: "1f638",
|
avatar: "1f638",
|
||||||
name: "文案写手",
|
name: "文案写手",
|
||||||
|
@@ -31,7 +31,7 @@ export const EN_MASKS: BuiltinMask[] = [
|
|||||||
],
|
],
|
||||||
modelConfig: {
|
modelConfig: {
|
||||||
model: "gpt-4",
|
model: "gpt-4",
|
||||||
temperature: 1,
|
temperature: 0.5,
|
||||||
max_tokens: 2000,
|
max_tokens: 2000,
|
||||||
presence_penalty: 0,
|
presence_penalty: 0,
|
||||||
sendMemory: true,
|
sendMemory: true,
|
||||||
|
@@ -43,7 +43,7 @@ const makeRequestParam = (
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
function getHeaders() {
|
export function getHeaders() {
|
||||||
const accessStore = useAccessStore.getState();
|
const accessStore = useAccessStore.getState();
|
||||||
let headers: Record<string, string> = {};
|
let headers: Record<string, string> = {};
|
||||||
|
|
||||||
|
@@ -1,7 +1,9 @@
|
|||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
import { persist } from "zustand/middleware";
|
import { persist } from "zustand/middleware";
|
||||||
import { StoreKey } from "../constant";
|
import { StoreKey } from "../constant";
|
||||||
|
import { getHeaders } from "../requests";
|
||||||
import { BOT_HELLO } from "./chat";
|
import { BOT_HELLO } from "./chat";
|
||||||
|
import { ALL_MODELS } from "./config";
|
||||||
|
|
||||||
export interface AccessControlStore {
|
export interface AccessControlStore {
|
||||||
accessCode: string;
|
accessCode: string;
|
||||||
@@ -54,12 +56,23 @@ export const useAccessStore = create<AccessControlStore>()(
|
|||||||
fetch("/api/config", {
|
fetch("/api/config", {
|
||||||
method: "post",
|
method: "post",
|
||||||
body: null,
|
body: null,
|
||||||
|
headers: {
|
||||||
|
...getHeaders(),
|
||||||
|
},
|
||||||
})
|
})
|
||||||
.then((res) => res.json())
|
.then((res) => res.json())
|
||||||
.then((res: DangerConfig) => {
|
.then((res: DangerConfig) => {
|
||||||
console.log("[Config] got config from server", res);
|
console.log("[Config] got config from server", res);
|
||||||
set(() => ({ ...res }));
|
set(() => ({ ...res }));
|
||||||
|
|
||||||
|
if (!res.enableGPT4) {
|
||||||
|
ALL_MODELS.forEach((model) => {
|
||||||
|
if (model.name.startsWith("gpt-4")) {
|
||||||
|
(model as any).available = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if ((res as any).botHello) {
|
if ((res as any).botHello) {
|
||||||
BOT_HELLO.content = (res as any).botHello;
|
BOT_HELLO.content = (res as any).botHello;
|
||||||
}
|
}
|
||||||
|
@@ -180,8 +180,9 @@ export const useChatStore = create<ChatStore>()(
|
|||||||
const sessions = get().sessions.slice();
|
const sessions = get().sessions.slice();
|
||||||
sessions.splice(index, 1);
|
sessions.splice(index, 1);
|
||||||
|
|
||||||
|
const currentIndex = get().currentSessionIndex;
|
||||||
let nextIndex = Math.min(
|
let nextIndex = Math.min(
|
||||||
get().currentSessionIndex,
|
currentIndex - Number(index < currentIndex),
|
||||||
sessions.length - 1,
|
sessions.length - 1,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -251,9 +252,20 @@ export const useChatStore = create<ChatStore>()(
|
|||||||
model: modelConfig.model,
|
model: modelConfig.model,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const systemInfo = createMessage({
|
||||||
|
role: "system",
|
||||||
|
content: `IMPRTANT: You are a virtual assistant powered by the ${
|
||||||
|
modelConfig.model
|
||||||
|
} model, now time is ${new Date().toLocaleString()}}`,
|
||||||
|
id: botMessage.id! + 1,
|
||||||
|
});
|
||||||
|
|
||||||
// get recent messages
|
// get recent messages
|
||||||
|
const systemMessages = [systemInfo];
|
||||||
const recentMessages = get().getMessagesWithMemory();
|
const recentMessages = get().getMessagesWithMemory();
|
||||||
const sendMessages = recentMessages.concat(userMessage);
|
const sendMessages = systemMessages.concat(
|
||||||
|
recentMessages.concat(userMessage),
|
||||||
|
);
|
||||||
const sessionIndex = get().currentSessionIndex;
|
const sessionIndex = get().currentSessionIndex;
|
||||||
const messageIndex = get().currentSession().messages.length + 1;
|
const messageIndex = get().currentSession().messages.length + 1;
|
||||||
|
|
||||||
@@ -390,14 +402,17 @@ export const useChatStore = create<ChatStore>()(
|
|||||||
|
|
||||||
summarizeSession() {
|
summarizeSession() {
|
||||||
const session = get().currentSession();
|
const session = get().currentSession();
|
||||||
|
|
||||||
|
// remove error messages if any
|
||||||
|
const cleanMessages = session.messages.filter((msg) => !msg.isError);
|
||||||
|
|
||||||
// should summarize topic after chating more than 50 words
|
// should summarize topic after chating more than 50 words
|
||||||
const SUMMARIZE_MIN_LEN = 50;
|
const SUMMARIZE_MIN_LEN = 50;
|
||||||
if (
|
if (
|
||||||
session.topic === DEFAULT_TOPIC &&
|
session.topic === DEFAULT_TOPIC &&
|
||||||
countMessages(session.messages) >= SUMMARIZE_MIN_LEN
|
countMessages(cleanMessages) >= SUMMARIZE_MIN_LEN
|
||||||
) {
|
) {
|
||||||
requestWithPrompt(session.messages, Locale.Store.Prompt.Topic, {
|
requestWithPrompt(cleanMessages, Locale.Store.Prompt.Topic, {
|
||||||
model: "gpt-3.5-turbo",
|
model: "gpt-3.5-turbo",
|
||||||
}).then((res) => {
|
}).then((res) => {
|
||||||
get().updateCurrentSession(
|
get().updateCurrentSession(
|
||||||
@@ -408,10 +423,10 @@ export const useChatStore = create<ChatStore>()(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const modelConfig = session.mask.modelConfig;
|
const modelConfig = session.mask.modelConfig;
|
||||||
let toBeSummarizedMsgs = session.messages.slice(
|
let toBeSummarizedMsgs = cleanMessages.slice(
|
||||||
session.lastSummarizeIndex,
|
session.lastSummarizeIndex,
|
||||||
);
|
);
|
||||||
|
|
||||||
const historyMsgLength = countMessages(toBeSummarizedMsgs);
|
const historyMsgLength = countMessages(toBeSummarizedMsgs);
|
||||||
|
|
||||||
if (historyMsgLength > modelConfig?.max_tokens ?? 4000) {
|
if (historyMsgLength > modelConfig?.max_tokens ?? 4000) {
|
||||||
|
@@ -76,6 +76,26 @@ export const ALL_MODELS = [
|
|||||||
name: "gpt-3.5-turbo-0301",
|
name: "gpt-3.5-turbo-0301",
|
||||||
available: true,
|
available: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "qwen-v1", // 通义千问
|
||||||
|
available: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ernie", // 文心一言
|
||||||
|
available: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "spark", // 讯飞星火
|
||||||
|
available: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "llama", // llama
|
||||||
|
available: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "chatglm", // chatglm-6b
|
||||||
|
available: false,
|
||||||
|
},
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export type ModelType = (typeof ALL_MODELS)[number]["name"];
|
export type ModelType = (typeof ALL_MODELS)[number]["name"];
|
||||||
|
@@ -158,15 +158,15 @@ export function autoGrowTextArea(dom: HTMLTextAreaElement) {
|
|||||||
|
|
||||||
const width = getDomContentWidth(dom);
|
const width = getDomContentWidth(dom);
|
||||||
measureDom.style.width = width + "px";
|
measureDom.style.width = width + "px";
|
||||||
measureDom.innerText = dom.value.trim().length > 0 ? dom.value : "1";
|
measureDom.innerText = dom.value !== "" ? dom.value : "1";
|
||||||
|
const endWithEmptyLine = dom.value.endsWith("\n");
|
||||||
const lineWrapCount = Math.max(0, dom.value.split("\n").length - 1);
|
|
||||||
const height = parseFloat(window.getComputedStyle(measureDom).height);
|
const height = parseFloat(window.getComputedStyle(measureDom).height);
|
||||||
const singleLineHeight = parseFloat(
|
const singleLineHeight = parseFloat(
|
||||||
window.getComputedStyle(singleLineDom).height,
|
window.getComputedStyle(singleLineDom).height,
|
||||||
);
|
);
|
||||||
|
|
||||||
const rows = Math.round(height / singleLineHeight) + lineWrapCount;
|
const rows =
|
||||||
|
Math.round(height / singleLineHeight) + (endWithEmptyLine ? 1 : 0);
|
||||||
|
|
||||||
return rows;
|
return rows;
|
||||||
}
|
}
|
||||||
|
39
docs/cloudflare-pages-cn.md
Normal file
39
docs/cloudflare-pages-cn.md
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# Cloudflare Pages 部署指南
|
||||||
|
|
||||||
|
## 如何新建项目
|
||||||
|
在 Github 上 fork 本项目,然后登录到 dash.cloudflare.com 并进入 Pages。
|
||||||
|
|
||||||
|
1. 点击 "Create a project"。
|
||||||
|
2. 选择 "Connect to Git"。
|
||||||
|
3. 关联 Cloudflare Pages 和你的 GitHub 账号。
|
||||||
|
4. 选中你 fork 的此项目。
|
||||||
|
5. 点击 "Begin setup"。
|
||||||
|
6. 对于 "Project name" 和 "Production branch",可以使用默认值,也可以根据需要进行更改。
|
||||||
|
7. 在 "Build Settings" 中,选择 "Framework presets" 选项并选择 "Next.js"。
|
||||||
|
8. 由于 node:buffer 的 bug,暂时不要使用默认的 "Build command"。请使用以下命令:
|
||||||
|
```
|
||||||
|
npx https://prerelease-registry.devprod.cloudflare.dev/next-on-pages/runs/4930842298/npm-package-next-on-pages-230 --experimental-minify
|
||||||
|
```
|
||||||
|
9. 对于 "Build output directory",使用默认值并且不要修改。
|
||||||
|
10. 不要修改 "Root Directory"。
|
||||||
|
11. 对于 "Environment variables",点击 ">" 然后点击 "Add variable"。按照以下信息填写:
|
||||||
|
|
||||||
|
- `NODE_VERSION=20.1`
|
||||||
|
- `NEXT_TELEMETRY_DISABLE=1`
|
||||||
|
- `OPENAI_API_KEY=你自己的API Key`
|
||||||
|
- `YARN_VERSION=1.22.19`
|
||||||
|
- `PHP_VERSION=7.4`
|
||||||
|
|
||||||
|
根据实际需要,可以选择填写以下选项:
|
||||||
|
|
||||||
|
- `CODE= 可选填,访问密码,可以使用逗号隔开多个密码`
|
||||||
|
- `OPENAI_ORG_ID= 可选填,指定 OpenAI 中的组织 ID`
|
||||||
|
- `HIDE_USER_API_KEY=1 可选,不让用户自行填入 API Key`
|
||||||
|
- `DISABLE_GPT4=1 可选,不让用户使用 GPT-4`
|
||||||
|
|
||||||
|
12. 点击 "Save and Deploy"。
|
||||||
|
13. 点击 "Cancel deployment",因为需要填写 Compatibility flags。
|
||||||
|
14. 前往 "Build settings"、"Functions",找到 "Compatibility flags"。
|
||||||
|
15. 在 "Configure Production compatibility flag" 和 "Configure Preview compatibility flag" 中填写 "nodejs_compat"。
|
||||||
|
16. 前往 "Deployments",点击 "Retry deployment"。
|
||||||
|
17. Enjoy.
|
38
docs/cloudflare-pages-en.md
Normal file
38
docs/cloudflare-pages-en.md
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# Cloudflare Pages Deployment Guide
|
||||||
|
|
||||||
|
## How to create a new project
|
||||||
|
Fork this project on GitHub, then log in to dash.cloudflare.com and go to Pages.
|
||||||
|
|
||||||
|
1. Click "Create a project".
|
||||||
|
2. Choose "Connect to Git".
|
||||||
|
3. Connect Cloudflare Pages to your GitHub account.
|
||||||
|
4. Select the forked project.
|
||||||
|
5. Click "Begin setup".
|
||||||
|
6. For "Project name" and "Production branch", use the default values or change them as needed.
|
||||||
|
7. In "Build Settings", choose the "Framework presets" option and select "Next.js".
|
||||||
|
8. Do not use the default "Build command" due to a node:buffer bug. Instead, use the following command:
|
||||||
|
```
|
||||||
|
npx https://prerelease-registry.devprod.cloudflare.dev/next-on-pages/runs/4930842298/npm-package-next-on-pages-230 --experimental-minify
|
||||||
|
```
|
||||||
|
9. For "Build output directory", use the default value and do not modify it.
|
||||||
|
10. Do not modify "Root Directory".
|
||||||
|
11. For "Environment variables", click ">" and then "Add variable". Fill in the following information:
|
||||||
|
- `NODE_VERSION=20.1`
|
||||||
|
- `NEXT_TELEMETRY_DISABLE=1`
|
||||||
|
- `OPENAI_API_KEY=your_own_API_key`
|
||||||
|
- `YARN_VERSION=1.22.19`
|
||||||
|
- `PHP_VERSION=7.4`
|
||||||
|
|
||||||
|
Optionally fill in the following based on your needs:
|
||||||
|
|
||||||
|
- `CODE= Optional, access passwords, multiple passwords can be separated by commas`
|
||||||
|
- `OPENAI_ORG_ID= Optional, specify the organization ID in OpenAI`
|
||||||
|
- `HIDE_USER_API_KEY=1 Optional, do not allow users to enter their own API key`
|
||||||
|
- `DISABLE_GPT4=1 Optional, do not allow users to use GPT-4`
|
||||||
|
|
||||||
|
12. Click "Save and Deploy".
|
||||||
|
13. Click "Cancel deployment" because you need to fill in Compatibility flags.
|
||||||
|
14. Go to "Build settings", "Functions", and find "Compatibility flags".
|
||||||
|
15. Fill in "nodejs_compat" for both "Configure Production compatibility flag" and "Configure Preview compatibility flag".
|
||||||
|
16. Go to "Deployments" and click "Retry deployment".
|
||||||
|
17. Enjoy.
|
@@ -10,6 +10,10 @@ const nextConfig = {
|
|||||||
source: "/api/proxy/:path*",
|
source: "/api/proxy/:path*",
|
||||||
destination: "https://api.openai.com/:path*",
|
destination: "https://api.openai.com/:path*",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
source: "/google-fonts/:path*",
|
||||||
|
destination: "https://fonts.googleapis.com/:path*",
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const apiUrl = process.env.API_URL;
|
const apiUrl = process.env.API_URL;
|
||||||
|
Reference in New Issue
Block a user