This commit is contained in:
GH Action - Upstream Sync 2023-07-06 01:12:42 +00:00
commit e153cd5020
30 changed files with 712 additions and 90 deletions

View File

@ -1,3 +1,4 @@
import { useEffect } from "react";
import { useSearchParams } from "react-router-dom"; import { useSearchParams } from "react-router-dom";
import Locale from "./locales"; import Locale from "./locales";
@ -11,21 +12,22 @@ interface Commands {
export function useCommand(commands: Commands = {}) { export function useCommand(commands: Commands = {}) {
const [searchParams, setSearchParams] = useSearchParams(); const [searchParams, setSearchParams] = useSearchParams();
if (commands === undefined) return; useEffect(() => {
let shouldUpdate = false;
searchParams.forEach((param, name) => {
const commandName = name as keyof Commands;
if (typeof commands[commandName] === "function") {
commands[commandName]!(param);
searchParams.delete(name);
shouldUpdate = true;
}
});
let shouldUpdate = false; if (shouldUpdate) {
searchParams.forEach((param, name) => { setSearchParams(searchParams);
const commandName = name as keyof Commands;
if (typeof commands[commandName] === "function") {
commands[commandName]!(param);
searchParams.delete(name);
shouldUpdate = true;
} }
}); // eslint-disable-next-line react-hooks/exhaustive-deps
}, [searchParams, commands]);
if (shouldUpdate) {
setSearchParams(searchParams);
}
} }
interface ChatCommands { interface ChatCommands {

View File

@ -61,7 +61,14 @@ import Locale from "../locales";
import { IconButton } from "./button"; import { IconButton } from "./button";
import styles from "./chat.module.scss"; import styles from "./chat.module.scss";
import { ListItem, Modal, showConfirm, showPrompt, showToast } from "./ui-lib"; import {
ListItem,
Modal,
Selector,
showConfirm,
showPrompt,
showToast,
} from "./ui-lib";
import { useLocation, useNavigate } from "react-router-dom"; import { useLocation, useNavigate } from "react-router-dom";
import { LAST_INPUT_KEY, Path, REQUEST_TIMEOUT_MS } from "../constant"; import { LAST_INPUT_KEY, Path, REQUEST_TIMEOUT_MS } from "../constant";
import { Avatar } from "./emoji"; import { Avatar } from "./emoji";
@ -172,10 +179,29 @@ function PromptToast(props: {
function useSubmitHandler() { function useSubmitHandler() {
const config = useAppConfig(); const config = useAppConfig();
const submitKey = config.submitKey; const submitKey = config.submitKey;
const isComposing = useRef(false);
useEffect(() => {
const onCompositionStart = () => {
isComposing.current = true;
};
const onCompositionEnd = () => {
isComposing.current = false;
};
window.addEventListener("compositionstart", onCompositionStart);
window.addEventListener("compositionend", onCompositionEnd);
return () => {
window.removeEventListener("compositionstart", onCompositionStart);
window.removeEventListener("compositionend", onCompositionEnd);
};
}, []);
const shouldSubmit = (e: React.KeyboardEvent<HTMLTextAreaElement>) => { const shouldSubmit = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
if (e.key !== "Enter") return false; if (e.key !== "Enter") return false;
if (e.key === "Enter" && e.nativeEvent.isComposing) return false; if (e.key === "Enter" && (e.nativeEvent.isComposing || isComposing.current))
return false;
return ( return (
(config.submitKey === SubmitKey.AltEnter && e.altKey) || (config.submitKey === SubmitKey.AltEnter && e.altKey) ||
(config.submitKey === SubmitKey.CtrlEnter && e.ctrlKey) || (config.submitKey === SubmitKey.CtrlEnter && e.ctrlKey) ||
@ -385,16 +411,11 @@ export function ChatActions(props: {
// switch model // switch model
const currentModel = chatStore.currentSession().mask.modelConfig.model; const currentModel = chatStore.currentSession().mask.modelConfig.model;
function nextModel() { const models = useMemo(
const models = config.models.filter((m) => m.available).map((m) => m.name); () => config.models.filter((m) => m.available).map((m) => m.name),
const modelIndex = models.indexOf(currentModel); [config.models],
const nextIndex = (modelIndex + 1) % models.length; );
const nextModel = models[nextIndex]; const [showModelSelector, setShowModelSelector] = useState(false);
chatStore.updateCurrentSession((session) => {
session.mask.modelConfig.model = nextModel as ModelType;
session.mask.syncGlobalConfig = false;
});
}
return ( return (
<div className={styles["chat-input-actions"]}> <div className={styles["chat-input-actions"]}>
@ -466,10 +487,28 @@ export function ChatActions(props: {
/> />
<ChatAction <ChatAction
onClick={nextModel} onClick={() => setShowModelSelector(true)}
text={currentModel} text={currentModel}
icon={<RobotIcon />} icon={<RobotIcon />}
/> />
{showModelSelector && (
<Selector
items={models.map((m) => ({
title: m,
value: m,
}))}
onClose={() => setShowModelSelector(false)}
onSelection={(s) => {
if (s.length === 0) return;
chatStore.updateCurrentSession((session) => {
session.mask.modelConfig.model = s[0] as ModelType;
session.mask.syncGlobalConfig = false;
});
showToast(s[0]);
}}
/>
)}
</div> </div>
); );
} }

View File

@ -186,7 +186,7 @@
box-shadow: var(--card-shadow); box-shadow: var(--card-shadow);
border: var(--border-in-light); border: var(--border-in-light);
* { *:not(li) {
overflow: hidden; overflow: hidden;
} }
} }

View File

@ -1,7 +1,16 @@
/* eslint-disable @next/next/no-img-element */
import { ChatMessage, useAppConfig, useChatStore } from "../store"; import { ChatMessage, useAppConfig, useChatStore } from "../store";
import Locale from "../locales"; import Locale from "../locales";
import styles from "./exporter.module.scss"; import styles from "./exporter.module.scss";
import { List, ListItem, Modal, Select, showToast } from "./ui-lib"; import {
List,
ListItem,
Modal,
Select,
showImageModal,
showModal,
showToast,
} from "./ui-lib";
import { IconButton } from "./button"; import { IconButton } from "./button";
import { copyToClipboard, downloadAs, useMobileScreen } from "../utils"; import { copyToClipboard, downloadAs, useMobileScreen } from "../utils";
@ -23,6 +32,7 @@ import { DEFAULT_MASK_AVATAR } from "../store/mask";
import { api } from "../client/api"; import { api } from "../client/api";
import { prettyObject } from "../utils/format"; import { prettyObject } from "../utils/format";
import { EXPORT_MESSAGE_CLASS_NAME } from "../constant"; import { EXPORT_MESSAGE_CLASS_NAME } from "../constant";
import { getClientConfig } from "../config/client";
const Markdown = dynamic(async () => (await import("./markdown")).Markdown, { const Markdown = dynamic(async () => (await import("./markdown")).Markdown, {
loading: () => <LoadingIcon />, loading: () => <LoadingIcon />,
@ -369,6 +379,7 @@ export function ImagePreviewer(props: {
const previewRef = useRef<HTMLDivElement>(null); const previewRef = useRef<HTMLDivElement>(null);
const copy = () => { const copy = () => {
showToast(Locale.Export.Image.Toast);
const dom = previewRef.current; const dom = previewRef.current;
if (!dom) return; if (!dom) return;
toBlob(dom).then((blob) => { toBlob(dom).then((blob) => {
@ -393,17 +404,15 @@ export function ImagePreviewer(props: {
const isMobile = useMobileScreen(); const isMobile = useMobileScreen();
const download = () => { const download = () => {
showToast(Locale.Export.Image.Toast);
const dom = previewRef.current; const dom = previewRef.current;
if (!dom) return; if (!dom) return;
toPng(dom) toPng(dom)
.then((blob) => { .then((blob) => {
if (!blob) return; if (!blob) return;
if (isMobile) { if (isMobile || getClientConfig()?.isApp) {
const image = new Image(); showImageModal(blob);
image.src = blob;
const win = window.open("");
win?.document.write(image.outerHTML);
} else { } else {
const link = document.createElement("a"); const link = document.createElement("a");
link.download = `${props.topic}.png`; link.download = `${props.topic}.png`;

View File

@ -12,6 +12,7 @@ import mermaid from "mermaid";
import LoadingIcon from "../icons/three-dots.svg"; import LoadingIcon from "../icons/three-dots.svg";
import React from "react"; import React from "react";
import { useDebouncedCallback, useThrottledCallback } from "use-debounce"; import { useDebouncedCallback, useThrottledCallback } from "use-debounce";
import { showImageModal } from "./ui-lib";
export function Mermaid(props: { code: string }) { export function Mermaid(props: { code: string }) {
const ref = useRef<HTMLDivElement>(null); const ref = useRef<HTMLDivElement>(null);
@ -37,11 +38,13 @@ export function Mermaid(props: { code: string }) {
if (!svg) return; if (!svg) return;
const text = new XMLSerializer().serializeToString(svg); const text = new XMLSerializer().serializeToString(svg);
const blob = new Blob([text], { type: "image/svg+xml" }); const blob = new Blob([text], { type: "image/svg+xml" });
const url = URL.createObjectURL(blob); console.log(blob);
const win = window.open(url); // const url = URL.createObjectURL(blob);
if (win) { // const win = window.open(url);
win.onload = () => URL.revokeObjectURL(url); // if (win) {
} // win.onload = () => URL.revokeObjectURL(url);
// }
showImageModal(URL.createObjectURL(blob));
} }
if (hasError) { if (hasError) {

View File

@ -30,7 +30,7 @@ import { useNavigate } from "react-router-dom";
import chatStyle from "./chat.module.scss"; import chatStyle from "./chat.module.scss";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { downloadAs, readFromFile } from "../utils"; import { copyToClipboard, downloadAs, readFromFile } from "../utils";
import { Updater } from "../typing"; import { Updater } from "../typing";
import { ModelConfigList } from "./model-config"; import { ModelConfigList } from "./model-config";
import { FileName, Path } from "../constant"; import { FileName, Path } from "../constant";
@ -65,6 +65,11 @@ export function MaskConfig(props: {
}); });
}; };
const copyMaskLink = () => {
const maskLink = `${location.protocol}//${location.host}/#${Path.NewChat}?mask=${props.mask.id}`;
copyToClipboard(maskLink);
};
const globalConfig = useAppConfig(); const globalConfig = useAppConfig();
return ( return (
@ -125,6 +130,20 @@ export function MaskConfig(props: {
}} }}
></input> ></input>
</ListItem> </ListItem>
{!props.shouldSyncFromGlobal ? (
<ListItem
title={Locale.Mask.Config.Share.Title}
subTitle={Locale.Mask.Config.Share.SubTitle}
>
<IconButton
icon={<CopyIcon />}
text={Locale.Mask.Config.Share.Action}
onClick={copyMaskLink}
/>
</ListItem>
) : null}
{props.shouldSyncFromGlobal ? ( {props.shouldSyncFromGlobal ? (
<ListItem <ListItem
title={Locale.Mask.Config.Sync.Title} title={Locale.Mask.Config.Sync.Title}

View File

@ -15,6 +15,7 @@ import { useAppConfig, useChatStore } from "../store";
import { MaskAvatar } from "./mask"; import { MaskAvatar } from "./mask";
import { useCommand } from "../command"; import { useCommand } from "../command";
import { showConfirm } from "./ui-lib"; import { showConfirm } from "./ui-lib";
import { BUILTIN_MASK_STORE } from "../masks";
function getIntersectionArea(aRect: DOMRect, bRect: DOMRect) { function getIntersectionArea(aRect: DOMRect, bRect: DOMRect) {
const xmin = Math.max(aRect.x, bRect.x); const xmin = Math.max(aRect.x, bRect.x);
@ -93,14 +94,17 @@ export function NewChat() {
const { state } = useLocation(); const { state } = useLocation();
const startChat = (mask?: Mask) => { const startChat = (mask?: Mask) => {
chatStore.newSession(mask); setTimeout(() => {
setTimeout(() => navigate(Path.Chat), 1); chatStore.newSession(mask);
navigate(Path.Chat);
}, 10);
}; };
useCommand({ useCommand({
mask: (id) => { mask: (id) => {
try { try {
const mask = maskStore.get(parseInt(id)); const intId = parseInt(id);
const mask = maskStore.get(intId) ?? BUILTIN_MASK_STORE.get(intId);
startChat(mask ?? undefined); startChat(mask ?? undefined);
} catch { } catch {
console.error("[New Chat] failed to create chat from mask id=", id); console.error("[New Chat] failed to create chat from mask id=", id);

View File

@ -542,10 +542,12 @@ export function Settings() {
} }
></input> ></input>
</ListItem> </ListItem>
</List>
<List>
<ListItem <ListItem
title={Locale.Settings.Mask.Title} title={Locale.Settings.Mask.Splash.Title}
subTitle={Locale.Settings.Mask.SubTitle} subTitle={Locale.Settings.Mask.Splash.SubTitle}
> >
<input <input
type="checkbox" type="checkbox"
@ -559,6 +561,22 @@ export function Settings() {
} }
></input> ></input>
</ListItem> </ListItem>
<ListItem
title={Locale.Settings.Mask.Builtin.Title}
subTitle={Locale.Settings.Mask.Builtin.SubTitle}
>
<input
type="checkbox"
checked={config.hideBuiltinMasks}
onChange={(e) =>
updateConfig(
(config) =>
(config.hideBuiltinMasks = e.currentTarget.checked),
)
}
></input>
</ListItem>
</List> </List>
<List> <List>

View File

@ -62,6 +62,7 @@
box-shadow: var(--card-shadow); box-shadow: var(--card-shadow);
margin-bottom: 20px; margin-bottom: 20px;
animation: slide-in ease 0.3s; animation: slide-in ease 0.3s;
background: var(--white);
} }
.list .list-item:last-child { .list .list-item:last-child {
@ -79,6 +80,19 @@
--modal-padding: 20px; --modal-padding: 20px;
&-max {
width: 95vw;
max-width: unset;
height: 95vh;
display: flex;
flex-direction: column;
.modal-content {
max-height: unset !important;
flex-grow: 1;
}
}
.modal-header { .modal-header {
padding: var(--modal-padding); padding: var(--modal-padding);
display: flex; display: flex;
@ -91,11 +105,19 @@
font-size: 16px; font-size: 16px;
} }
.modal-close-btn { .modal-header-actions {
cursor: pointer; display: flex;
&:hover { .modal-header-action {
filter: brightness(1.2); cursor: pointer;
&:not(:last-child) {
margin-right: 20px;
}
&:hover {
filter: brightness(1.2);
}
} }
} }
} }
@ -249,3 +271,34 @@
border: 1px solid var(--primary); border: 1px solid var(--primary);
} }
} }
.selector {
position: fixed;
top: 0;
left: 0;
height: 100vh;
width: 100vw;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
&-content {
.list {
overflow: hidden;
.list-item {
cursor: pointer;
background-color: var(--white);
&:hover {
filter: brightness(0.95);
}
&:active {
filter: brightness(0.9);
}
}
}
}
}

View File

@ -1,3 +1,4 @@
/* eslint-disable @next/next/no-img-element */
import styles from "./ui-lib.module.scss"; import styles from "./ui-lib.module.scss";
import LoadingIcon from "../icons/three-dots.svg"; import LoadingIcon from "../icons/three-dots.svg";
import CloseIcon from "../icons/close.svg"; import CloseIcon from "../icons/close.svg";
@ -6,6 +7,8 @@ import EyeOffIcon from "../icons/eye-off.svg";
import DownIcon from "../icons/down.svg"; import DownIcon from "../icons/down.svg";
import ConfirmIcon from "../icons/confirm.svg"; import ConfirmIcon from "../icons/confirm.svg";
import CancelIcon from "../icons/cancel.svg"; import CancelIcon from "../icons/cancel.svg";
import MaxIcon from "../icons/max.svg";
import MinIcon from "../icons/min.svg";
import Locale from "../locales"; import Locale from "../locales";
@ -44,9 +47,13 @@ export function ListItem(props: {
children?: JSX.Element | JSX.Element[]; children?: JSX.Element | JSX.Element[];
icon?: JSX.Element; icon?: JSX.Element;
className?: string; className?: string;
onClick?: () => void;
}) { }) {
return ( return (
<div className={styles["list-item"] + ` ${props.className || ""}`}> <div
className={styles["list-item"] + ` ${props.className || ""}`}
onClick={props.onClick}
>
<div className={styles["list-header"]}> <div className={styles["list-header"]}>
{props.icon && <div className={styles["list-icon"]}>{props.icon}</div>} {props.icon && <div className={styles["list-icon"]}>{props.icon}</div>}
<div className={styles["list-item-title"]}> <div className={styles["list-item-title"]}>
@ -93,6 +100,7 @@ interface ModalProps {
title: string; title: string;
children?: any; children?: any;
actions?: JSX.Element[]; actions?: JSX.Element[];
defaultMax?: boolean;
onClose?: () => void; onClose?: () => void;
} }
export function Modal(props: ModalProps) { export function Modal(props: ModalProps) {
@ -111,13 +119,30 @@ export function Modal(props: ModalProps) {
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, []); }, []);
const [isMax, setMax] = useState(!!props.defaultMax);
return ( return (
<div className={styles["modal-container"]}> <div
className={
styles["modal-container"] + ` ${isMax && styles["modal-container-max"]}`
}
>
<div className={styles["modal-header"]}> <div className={styles["modal-header"]}>
<div className={styles["modal-title"]}>{props.title}</div> <div className={styles["modal-title"]}>{props.title}</div>
<div className={styles["modal-close-btn"]} onClick={props.onClose}> <div className={styles["modal-header-actions"]}>
<CloseIcon /> <div
className={styles["modal-header-action"]}
onClick={() => setMax(!isMax)}
>
{isMax ? <MinIcon /> : <MaxIcon />}
</div>
<div
className={styles["modal-header-action"]}
onClick={props.onClose}
>
<CloseIcon />
</div>
</div> </div>
</div> </div>
@ -394,3 +419,54 @@ export function showPrompt(content: any, value = "", rows = 3) {
); );
}); });
} }
export function showImageModal(img: string) {
showModal({
title: Locale.Export.Image.Modal,
children: (
<div>
<img
src={img}
alt="preview"
style={{
maxWidth: "100%",
}}
></img>
</div>
),
});
}
export function Selector<T>(props: {
items: Array<{
title: string;
subTitle?: string;
value: T;
}>;
onSelection?: (selection: T[]) => void;
onClose?: () => void;
multiple?: boolean;
}) {
return (
<div className={styles["selector"]} onClick={() => props.onClose?.()}>
<div className={styles["selector-content"]}>
<List>
{props.items.map((item, i) => {
return (
<ListItem
className={styles["selector-item"]}
key={i}
title={item.title}
subTitle={item.subTitle}
onClick={() => {
props.onSelection?.([item.value]);
props.onClose?.();
}}
></ListItem>
);
})}
</List>
</div>
</div>
);
}

View File

@ -66,27 +66,27 @@ Current time: {{time}}`;
export const DEFAULT_MODELS = [ export const DEFAULT_MODELS = [
{ {
name: "gpt-4", name: "gpt-4",
available: false, available: true,
}, },
{ {
name: "gpt-4-0314", name: "gpt-4-0314",
available: false, available: true,
}, },
{ {
name: "gpt-4-0613", name: "gpt-4-0613",
available: false, available: true,
}, },
{ {
name: "gpt-4-32k", name: "gpt-4-32k",
available: false, available: true,
}, },
{ {
name: "gpt-4-32k-0314", name: "gpt-4-32k-0314",
available: false, available: true,
}, },
{ {
name: "gpt-4-32k-0613", name: "gpt-4-32k-0613",
available: false, available: true,
}, },
{ {
name: "gpt-3.5-turbo", name: "gpt-3.5-turbo",

View File

@ -130,8 +130,10 @@ const ar: PartialLocaleType = {
SubTitle: "معاينة Markdown في فقاعة", SubTitle: "معاينة Markdown في فقاعة",
}, },
Mask: { Mask: {
Title: "شاشة تظهر الأقنعة", Splash: {
SubTitle: "عرض شاشة تظهر الأقنعة قبل بدء الدردشة الجديدة", Title: "شاشة تظهر الأقنعة",
SubTitle: "عرض شاشة تظهر الأقنعة قبل بدء الدردشة الجديدة",
},
}, },
Prompt: { Prompt: {
Disable: { Disable: {

338
app/locales/bn.ts Normal file
View File

@ -0,0 +1,338 @@
import { SubmitKey } from "../store/config";
import { PartialLocaleType } from "./index";
const bn: PartialLocaleType = {
WIP: "শীঘ্রই আসছে...",
Error: {
Unauthorized:
"অননুমোদিত অ্যাক্সেস, অনুগ্রহ করে [অথোরাইজশন](/#/auth) পৃষ্ঠায় অ্যাক্সেস কোড ইনপুট করুন।",
},
Auth: {
Title: "একটি অ্যাক্সেস কোড প্রয়োজন",
Tips: "নীচে অ্যাক্সেস কোড ইনপুট করুন",
Input: "অ্যাক্সেস কোড",
Confirm: "নিশ্চিত করুন",
Later: "পরে",
},
ChatItem: {
ChatItemCount: (count: number) => `${count} টি বার্তা`,
},
Chat: {
SubTitle: (count: number) => `${count} টি বার্তা`,
Actions: {
ChatList: "চ্যাট তালিকায় যান",
CompressedHistory: "সংক্ষিপ্ত ইতিহাস মেমোরি প্রম্পট",
Export: "সমস্ত বার্তা মার্কডাউন হিসাবে রপ্তানি করুন",
Copy: "কপি",
Stop: "বন্ধ করুন",
Retry: "পুনরায় চেষ্টা করুন",
Pin: "পিন করুন",
PinToastContent: "পিন করা হয়েছে ২টি বার্তা প্রম্পটে",
PinToastAction: "দেখুন",
Delete: "মুছে ফেলুন",
Edit: "সম্পাদন করুন",
},
Commands: {
new: "নতুন চ্যাট শুরু করুন",
newm: "মাস্ক সহ নতুন চ্যাট শুরু করুন",
next: "পরবর্তী চ্যাট",
prev: "পূর্ববর্তী চ্যাট",
clear: "সংশ্লিষ্টতাবদ্ধকরণ পরিষ্কার করুন",
del: "চ্যাট মুছুন",
},
InputActions: {
Stop: "বন্ধ করুন",
ToBottom: "সর্বশেষতম দিকে",
Theme: {
auto: "অটো",
light: "হালকা থিম",
dark: "ডার্ক থিম",
},
Prompt: "প্রম্পটগুলিতে",
Masks: "মাস্কগুলি",
Clear: "সংশ্লিষ্টতাবদ্ধকরণ পরিষ্কার করুন",
Settings: "সেটিংস",
},
Rename: "চ্যাট পুনঃনামকরণ করুন",
Typing: "টাইপিং...",
Input: (submitKey: string) => {
var inputHints = `${submitKey} to send`;
if (submitKey === String(SubmitKey.Enter)) {
inputHints += ", Shift + Enter to wrap";
}
return inputHints + ", / to search prompts, : to use commands";
},
Send: "প্রেরণ করুন",
Config: {
Reset: "ডিফল্টে রিসেট করুন",
SaveAs: "মাস্ক হিসাবে সংরক্ষণ করুন",
},
},
Export: {
Title: "বার্তা রপ্তানিকরণ",
Copy: "সমস্তটি কপি করুন",
Download: "ডাউনলোড করুন",
MessageFromYou: "আপনার বার্তা",
MessageFromChatGPT: "চ্যাটজিপিটির বার্তা",
Share: "শেয়ার করুন শেয়ারজিপিটি তে",
Format: {
Title: "রপ্তানি ফরম্যাট",
SubTitle: "মার্কডাউন বা পিএনজি চিত্র",
},
IncludeContext: {
Title: "মাস্ক অন্তর্ভুক্ত করুন",
SubTitle: "মাস্কগুলি সংরক্ষণ করবেন না কি",
},
Steps: {
Select: "নির্বাচন করুন",
Preview: "প্রিভিউ করুন",
},
},
Select: {
Search: "অনুসন্ধান করুন",
All: "সমস্তটি নির্বাচন করুন",
Latest: "সর্বশেষতমটি নির্বাচন করুন",
Clear: "পরিষ্কার করুন",
},
Memory: {
Title: "মেমোরি প্রম্পট",
EmptyContent: "এখনও কিছুই নেই।",
Send: "মেমোরি প্রেরণ করুন",
Copy: "মেমোরি কপি করুন",
Reset: "পুনরায় নিশ্চিত করুন",
ResetConfirm:
"রিসেট করলে বর্তমান চ্যাট ইতিহাস এবং ঐতিহাসিক মেমোরি মুছে যাবে। পুনরায় নির্দিষ্ট করতে চান তা নিশ্চিত করতে চান?",
},
Home: {
NewChat: "নতুন চ্যাট",
DeleteChat: "নির্বাচিত সংলাপটি মুছতে নিশ্চিত করুন?",
DeleteToast: "চ্যাটটি মুছেছেন",
Revert: "পুনরায়",
},
Settings: {
Title: "সেটিংস",
SubTitle: "সমস্ত সেটিংস",
Danger: {
Reset: {
Title: "সমস্ত সেটিংস পুনঃনির্দেশ দিন",
SubTitle: "সকল সেটিংস ডিফল্টে পুনঃনির্দেশ দিতে",
Action: "পুনঃনির্দেশ দিন",
Confirm: "সমস্ত সেটিংস ডিফল্টে পুনঃনির্দেশ করতে নিশ্চিত করতে?",
},
Clear: {
Title: "সমস্ত তথ্য মুছুন",
SubTitle: "সমস্ত বার্তা এবং সেটিংস মুছুন",
Action: "মুছুন",
Confirm: "সমস্ত বার্তা এবং সেটিংস মুছে ফেলতে নিশ্চিত করতে?",
},
},
Lang: {
Name: "বাংলা", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language`
All: "সমস্ত ভাষা",
},
Avatar: "অবতার",
FontSize: {
Title: "ফন্ট সাইজ",
SubTitle: "চ্যাট সামগ্রীর ফন্ট সাইজ সংশোধন করুন",
},
InputTemplate: {
Title: "ইনপুট টেমপ্লেট",
SubTitle: "নতুনতম বার্তা এই টেমপ্লেটে পূরণ হবে",
},
Update: {
Version: (x: string) => `Version: ${x}`,
IsLatest: "Latest version",
CheckUpdate: "Check Update",
IsChecking: "Checking update...",
FoundUpdate: (x: string) => `Found new version: ${x}`,
GoToUpdate: "Update",
},
SendKey: "প্রেরণ চাবি",
Theme: "থিম",
TightBorder: "সঙ্গতি সীমা",
SendPreviewBubble: {
Title: "প্রিভিউ বুলবুল প্রেরণ করুন",
SubTitle: "বুলবুলে মার্কডাউন প্রিভিউ করুন",
},
Mask: {
Splash: {
Title: "মাস্ক স্প্ল্যাশ স্ক্রিন",
SubTitle:
"নতুন চ্যাট শুরু করার আগে মাস্ক স্প্ল্যাশ স্ক্রিন প্রদর্শন করুন",
},
Builtin: {
Title: "মূলত মাস্ক গোপন করুন",
SubTitle: "মাস্ক তালিকা থেকে মূলত মাস্কগুলি লুকান",
},
},
Prompt: {
Disable: {
Title: "অটো-সম্পূর্ণতা নিষ্ক্রিয় করুন",
SubTitle: "অটো-সম্পূর্ণতা চালু করতে / ইনপুট করুন",
},
List: "প্রম্পট তালিকা",
ListCount: (builtin: number, custom: number) =>
`${builtin} built-in, ${custom} user-defined`,
Edit: "সম্পাদন করুন",
Modal: {
Title: "প্রম্পট তালিকা",
Add: "একটি যোগ করুন",
Search: "সন্ধান প্রম্পট",
},
EditModal: {
Title: "সম্পাদন করুন প্রম্পট",
},
},
HistoryCount: {
Title: "সংযুক্ত বার্তা সংখ্যা",
SubTitle: "প্রতি অনুরোধে প্রেরণ করা গেলে প্রেরণ করা হবে",
},
CompressThreshold: {
Title: "ইতিহাস সঙ্কুচিত করার সীমা",
SubTitle:
"নকুল বার্তা দৈর্ঘ্য সীমা অতিক্রান্ত হলে ঐ বার্তাটি সঙ্কুচিত হবে",
},
Token: {
Title: "অ্যাপি কী",
SubTitle: "অ্যাক্সেস কোড সীমা উপেক্ষা করতে আপনার কীটি ব্যবহার করুন",
Placeholder: "OpenAI API কী",
},
Usage: {
Title: "একাউন্ট ব্যালেন্স",
SubTitle(used: any, total: any) {
return `এই মাসে ব্যবহৃত $${used}, সাবস্ক্রিপশন $${total}`;
},
IsChecking: "চেক করা হচ্ছে...",
Check: "চেক",
NoAccess: "ব্যালেন্স চেক করতে অ্যাপি কী ইনপুট করুন",
},
AccessCode: {
Title: "অ্যাক্সেস কোড",
SubTitle: "অ্যাক্সেস নিয়ন্ত্রণ সক্রিয়",
Placeholder: "অ্যাক্সেস কোড প্রয়োজন",
},
Endpoint: {
Title: "ইনটারপয়েন্ট",
SubTitle: "কাস্টম এন্ডপয়েন্টটি হতে হবে http(s):// দিয়ে শুরু হতে হবে",
},
Model: "মডেল",
Temperature: {
Title: "তাপমাত্রা",
SubTitle: "আরতি মান বেশি করলে বেশি এলোমেলো আউটপুট হবে",
},
TopP: {
Title: "শীর্ষ পি",
SubTitle: "তাপমাত্রা সঙ্গে এই মান পরিবর্তন করবেন না",
},
MaxTokens: {
Title: "সর্বাধিক টোকেন",
SubTitle: "ইনপুট টোকেন এবং উৎপাদিত টোকেনের সর্বাধিক দৈর্ঘ্য",
},
PresencePenalty: {
Title: "উপস্থিতির জরিমানা",
SubTitle: "আরতি মান বেশি করলে নতুন বিষয়গুলি সম্ভাব্যতা বাড়াতে পারে",
},
FrequencyPenalty: {
Title: "ফ্রিকুয়েন্সি জরিমানা",
SubTitle:
"আরতি মান বাড়ালে একই লাইন পুনরায় ব্যাবহার করার সম্ভাবনা হ্রাস পায়",
},
},
Store: {
DefaultTopic: "নতুন সংলাপ",
BotHello: "হ্যালো! আজকে আপনাকে কিভাবে সাহায্য করতে পারি?",
Error: "কিছু নিয়ে ভুল হয়েছে, পরে আবার চেষ্টা করুন।",
Prompt: {
History: (content: string) =>
"এটি চ্যাট ইতিহাসের সংক্ষিপ্ত সংকলনের মতো: " + content,
Topic:
"আমাদের সংলাপটির চার থেকে পাঁচ শব্দের একটি শিরোনাম তৈরি করুন যা আমাদের আলাপের সংক্ষিপ্তসার হিসাবে যোগ হবে না, যেমন অভিবৃত্তি, বিন্যাস, উদ্ধৃতি, পূর্বচালক চিহ্ন, পূর্বরোবক্তির যেকোনো চিহ্ন বা অতিরিক্ত পাঠ। মেয়াদশেষ উদ্ধৃতি চেষ্টা করুন।",
Summarize:
"২০০ শব্দের লম্বা হয়ে মুহূর্তে আলোচনা সংক্ষেপের রপ্তানি করুন, যেটি ভবিষ্যতের প্রম্পট হিসাবে ব্যবহার করবেন।",
},
},
Copy: {
Success: "ক্লিপবোর্ডে কপি করা হয়েছে",
Failed: "কপি ব্যর্থ, অনুমতি প্রদান করার জন্য অনুমতি প্রদান করুন",
},
Context: {
Toast: (x: any) => `With ${x} contextual prompts`,
Edit: "বর্তমান চ্যাট সেটিংস",
Add: "একটি প্রম্পট যোগ করুন",
Clear: "সঙ্গতি পরিস্কার করুন",
Revert: "পূর্ববর্তী অবস্থানে ফিরে যান",
},
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 ? "(readonly)" : ""}`,
Download: "ডাউনলোড করুন",
Clone: "ক্লোন করুন",
},
Config: {
Avatar: "বট অবতার",
Name: "বটের নাম",
Sync: {
Title: "গ্লোবাল কনফিগ ব্যবহার করুন",
SubTitle: "এই চ্যাটে গ্লোবাল কনফিগ ব্যবহার করুন",
Confirm:
"গ্লোবাল কনফিগ দ্বারা কাস্টম কনফিগ ওভাররাইড করতে নিশ্চিত করতে?",
},
HideContext: {
Title: "সংশ্লিষ্টতা প্রম্পটগুলি লুকান",
SubTitle: "চ্যাটে সংশ্লিষ্টতা প্রম্পটগুলি দেখাবেন না",
},
Share: {
Title: "এই মাস্কটি শেয়ার করুন",
SubTitle: "এই মাস্কের একটি লিঙ্ক তৈরি করুন",
Action: "লিঙ্ক কপি করুন",
},
},
},
NewChat: {
Return: "ফিরে যান",
Skip: "শুরু করুন",
Title: "মাস্ক নির্বাচন করুন",
SubTitle: "মাস্কের পিছনে আত্মার সঙ্গে চ্যাট করুন",
More: "আরো খুঁজুন",
NotShow: "এখনও দেখাবেন না",
ConfirmNoShow:
"নিষ্ক্রিয় করতে নিশ্চিত করুন? পরে আপনি এটি সেটিংসে সক্ষম করতে পারবেন।",
},
UI: {
Confirm: "নিশ্চিত করুন",
Cancel: "বাতিল করুন",
Close: "বন্ধ করুন",
Create: "তৈরি করুন",
Edit: "সম্পাদন করুন",
},
Exporter: {
Model: "মডেল",
Messages: "বার্তা",
Topic: "টপিক",
Time: "সময়",
},
};
export default bn;

View File

@ -86,6 +86,10 @@ const cn = {
Select: "选取", Select: "选取",
Preview: "预览", Preview: "预览",
}, },
Image: {
Toast: "正在生成截图",
Modal: "长按或右键保存图片",
},
}, },
Select: { Select: {
Search: "搜索消息", Search: "搜索消息",
@ -156,8 +160,14 @@ const cn = {
SubTitle: "在预览气泡中预览 Markdown 内容", SubTitle: "在预览气泡中预览 Markdown 内容",
}, },
Mask: { Mask: {
Title: "面具启动页", Splash: {
SubTitle: "新建聊天时,展示面具启动页", Title: "面具启动页",
SubTitle: "新建聊天时,展示面具启动页",
},
Builtin: {
Title: "隐藏内置面具",
SubTitle: "在所有面具列表中隐藏内置面具",
},
}, },
Prompt: { Prompt: {
Disable: { Disable: {
@ -291,6 +301,11 @@ const cn = {
Title: "隐藏预设对话", Title: "隐藏预设对话",
SubTitle: "隐藏后预设对话不会出现在聊天界面", SubTitle: "隐藏后预设对话不会出现在聊天界面",
}, },
Share: {
Title: "分享此面具",
SubTitle: "生成此面具的直达链接",
Action: "复制链接",
},
}, },
}, },
NewChat: { NewChat: {

View File

@ -87,8 +87,10 @@ const cs: PartialLocaleType = {
SubTitle: "Zobrazit v náhledu bubliny", SubTitle: "Zobrazit v náhledu bubliny",
}, },
Mask: { Mask: {
Title: "Úvodní obrazovka Masek", Splash: {
SubTitle: "Před zahájením nového chatu zobrazte úvodní obrazovku Masek", Title: "Úvodní obrazovka Masek",
SubTitle: "Před zahájením nového chatu zobrazte úvodní obrazovku Masek",
},
}, },
Prompt: { Prompt: {
Disable: { Disable: {

View File

@ -87,8 +87,10 @@ const de: PartialLocaleType = {
SubTitle: "Preview markdown in bubble", SubTitle: "Preview markdown in bubble",
}, },
Mask: { Mask: {
Title: "Mask Splash Screen", Splash: {
SubTitle: "Show a mask splash screen before starting new chat", Title: "Mask Splash Screen",
SubTitle: "Show a mask splash screen before starting new chat",
},
}, },
Prompt: { Prompt: {
Disable: { Disable: {

View File

@ -1,6 +1,7 @@
import { SubmitKey } from "../store/config"; import { SubmitKey } from "../store/config";
import { LocaleType } from "./index"; import { LocaleType } from "./index";
// if you are adding a new translation, please use PartialLocaleType instead of LocaleType
const en: LocaleType = { const en: LocaleType = {
WIP: "Coming Soon...", WIP: "Coming Soon...",
Error: { Error: {
@ -87,6 +88,10 @@ const en: LocaleType = {
Select: "Select", Select: "Select",
Preview: "Preview", Preview: "Preview",
}, },
Image: {
Toast: "Capturing Image...",
Modal: "Long press or right click to save image",
},
}, },
Select: { Select: {
Search: "Search", Search: "Search",
@ -157,8 +162,14 @@ const en: LocaleType = {
SubTitle: "Preview markdown in bubble", SubTitle: "Preview markdown in bubble",
}, },
Mask: { Mask: {
Title: "Mask Splash Screen", Splash: {
SubTitle: "Show a mask splash screen before starting new chat", Title: "Mask Splash Screen",
SubTitle: "Show a mask splash screen before starting new chat",
},
Builtin: {
Title: "Hide Builtin Masks",
SubTitle: "Hide builtin masks in mask list",
},
}, },
Prompt: { Prompt: {
Disable: { Disable: {
@ -295,6 +306,11 @@ const en: LocaleType = {
Title: "Hide Context Prompts", Title: "Hide Context Prompts",
SubTitle: "Do not show in-context prompts in chat", SubTitle: "Do not show in-context prompts in chat",
}, },
Share: {
Title: "Share This Mask",
SubTitle: "Generate a link to this mask",
Action: "Copy Link",
},
}, },
}, },
NewChat: { NewChat: {

View File

@ -87,8 +87,10 @@ const es: PartialLocaleType = {
SubTitle: "Preview markdown in bubble", SubTitle: "Preview markdown in bubble",
}, },
Mask: { Mask: {
Title: "Mask Splash Screen", Splash: {
SubTitle: "Show a mask splash screen before starting new chat", Title: "Mask Splash Screen",
SubTitle: "Show a mask splash screen before starting new chat",
},
}, },
Prompt: { Prompt: {
Disable: { Disable: {

View File

@ -88,9 +88,11 @@ const fr: PartialLocaleType = {
SubTitle: "Aperçu du Markdown dans une bulle", SubTitle: "Aperçu du Markdown dans une bulle",
}, },
Mask: { Mask: {
Title: "Écran de masque", Splash: {
SubTitle: Title: "Écran de masque",
"Afficher un écran de masque avant de démarrer une nouvelle discussion", SubTitle:
"Afficher un écran de masque avant de démarrer une nouvelle discussion",
},
}, },
Prompt: { Prompt: {
Disable: { Disable: {

View File

@ -13,6 +13,7 @@ import no from "./no";
import cs from "./cs"; import cs from "./cs";
import ko from "./ko"; import ko from "./ko";
import ar from "./ar"; import ar from "./ar";
import bn from "./bn";
import { merge } from "../utils/merge"; import { merge } from "../utils/merge";
import type { LocaleType } from "./cn"; import type { LocaleType } from "./cn";
@ -34,6 +35,7 @@ const ALL_LANGS = {
cs, cs,
no, no,
ar, ar,
bn,
}; };
export type Lang = keyof typeof ALL_LANGS; export type Lang = keyof typeof ALL_LANGS;
@ -56,6 +58,7 @@ export const ALL_LANG_OPTIONS: Record<Lang, string> = {
cs: "Čeština", cs: "Čeština",
no: "Nynorsk", no: "Nynorsk",
ar: "العربية", ar: "العربية",
bn: "বাংলা",
}; };
const LANG_KEY = "lang"; const LANG_KEY = "lang";

View File

@ -87,8 +87,10 @@ const it: PartialLocaleType = {
SubTitle: "Preview markdown in bubble", SubTitle: "Preview markdown in bubble",
}, },
Mask: { Mask: {
Title: "Mask Splash Screen", Splash: {
SubTitle: "Show a mask splash screen before starting new chat", Title: "Mask Splash Screen",
SubTitle: "Show a mask splash screen before starting new chat",
},
}, },
Prompt: { Prompt: {
Disable: { Disable: {

View File

@ -104,8 +104,10 @@ const jp: PartialLocaleType = {
SubTitle: "プレビューバブルでマークダウンコンテンツをプレビュー", SubTitle: "プレビューバブルでマークダウンコンテンツをプレビュー",
}, },
Mask: { Mask: {
Title: "キャラクターページ", Splash: {
SubTitle: "新規チャット作成時にキャラクターページを表示する", Title: "キャラクターページ",
SubTitle: "新規チャット作成時にキャラクターページを表示する",
},
}, },
Prompt: { Prompt: {
Disable: { Disable: {

View File

@ -87,8 +87,10 @@ const ko: PartialLocaleType = {
SubTitle: "버블에서 마크다운 미리 보기", SubTitle: "버블에서 마크다운 미리 보기",
}, },
Mask: { Mask: {
Title: "마스크 시작 화면", Splash: {
SubTitle: "새로운 채팅 시작 전에 마스크 시작 화면 표시", Title: "마스크 시작 화면",
SubTitle: "새로운 채팅 시작 전에 마스크 시작 화면 표시",
},
}, },
Prompt: { Prompt: {
Disable: { Disable: {

View File

@ -87,8 +87,10 @@ const ru: PartialLocaleType = {
SubTitle: "Предварительный просмотр markdown в пузыре", SubTitle: "Предварительный просмотр markdown в пузыре",
}, },
Mask: { Mask: {
Title: "Экран заставки маски", Splash: {
SubTitle: "Показывать экран заставки маски перед началом нового чата", Title: "Экран заставки маски",
SubTitle: "Показывать экран заставки маски перед началом нового чата",
},
}, },
Prompt: { Prompt: {
Disable: { Disable: {

View File

@ -87,8 +87,10 @@ const tr: PartialLocaleType = {
SubTitle: "Preview markdown in bubble", SubTitle: "Preview markdown in bubble",
}, },
Mask: { Mask: {
Title: "Mask Splash Screen", Splash: {
SubTitle: "Show a mask splash screen before starting new chat", Title: "Mask Splash Screen",
SubTitle: "Show a mask splash screen before starting new chat",
},
}, },
Prompt: { Prompt: {
Disable: { Disable: {

View File

@ -85,8 +85,10 @@ const tw: PartialLocaleType = {
SubTitle: "在预览气泡中预览 Markdown 内容", SubTitle: "在预览气泡中预览 Markdown 内容",
}, },
Mask: { Mask: {
Title: "面具启动页", Splash: {
SubTitle: "新建聊天时,展示面具启动页", Title: "面具启动页",
SubTitle: "新建聊天时,展示面具启动页",
},
}, },
Prompt: { Prompt: {
Disable: { Disable: {

View File

@ -87,8 +87,10 @@ const vi: PartialLocaleType = {
SubTitle: "Xem trước nội dung markdown bằng bong bóng", SubTitle: "Xem trước nội dung markdown bằng bong bóng",
}, },
Mask: { Mask: {
Title: "Mask Splash Screen", Splash: {
SubTitle: "Chớp màn hình khi bắt đầu cuộc trò chuyện mới", Title: "Mask Splash Screen",
SubTitle: "Chớp màn hình khi bắt đầu cuộc trò chuyện mới",
},
}, },
Prompt: { Prompt: {
Disable: { Disable: {

View File

@ -420,7 +420,7 @@ export const useChatStore = create<ChatStore>()(
modelConfig.sendMemory && modelConfig.sendMemory &&
session.memoryPrompt && session.memoryPrompt &&
session.memoryPrompt.length > 0 && session.memoryPrompt.length > 0 &&
session.lastSummarizeIndex <= clearContextIndex; session.lastSummarizeIndex > clearContextIndex;
const longTermMemoryPrompts = shouldSendLongTermMemory const longTermMemoryPrompts = shouldSendLongTermMemory
? [get().getMemoryPrompt()] ? [get().getMemoryPrompt()]
: []; : [];

View File

@ -32,6 +32,7 @@ export const DEFAULT_CONFIG = {
disablePromptHint: false, disablePromptHint: false,
dontShowMaskSplashScreen: false, // dont show splash screen when create chat dontShowMaskSplashScreen: false, // dont show splash screen when create chat
hideBuiltinMasks: false, // dont add builtin masks
models: DEFAULT_MODELS as any as LLMModel[], models: DEFAULT_MODELS as any as LLMModel[],
@ -136,9 +137,9 @@ export const useAppConfig = create<ChatConfigStore>()(
}), }),
{ {
name: StoreKey.Config, name: StoreKey.Config,
version: 3.3, version: 3.4,
migrate(persistedState, version) { migrate(persistedState, version) {
if (version === 3.3) return persistedState as any; if (version === 3.4) return persistedState as any;
const state = persistedState as ChatConfig; const state = persistedState as ChatConfig;
state.modelConfig.sendMemory = true; state.modelConfig.sendMemory = true;
@ -148,6 +149,7 @@ export const useAppConfig = create<ChatConfigStore>()(
state.modelConfig.top_p = 1; state.modelConfig.top_p = 1;
state.modelConfig.template = DEFAULT_INPUT_TEMPLATE; state.modelConfig.template = DEFAULT_INPUT_TEMPLATE;
state.dontShowMaskSplashScreen = false; state.dontShowMaskSplashScreen = false;
state.hideBuiltinMasks = false;
return state; return state;
}, },

View File

@ -90,6 +90,7 @@ export const useMaskStore = create<MaskStore>()(
(a, b) => b.id - a.id, (a, b) => b.id - a.id,
); );
const config = useAppConfig.getState(); const config = useAppConfig.getState();
if (config.hideBuiltinMasks) return userMasks;
const buildinMasks = BUILTIN_MASKS.map( const buildinMasks = BUILTIN_MASKS.map(
(m) => (m) =>
({ ({