Merge remote-tracking branch 'upstream/main'

This commit is contained in:
ZhaoLiu 2023-08-09 14:07:03 +08:00
commit 2d5a1ec13f
31 changed files with 186 additions and 148 deletions

View File

@ -135,7 +135,7 @@ After forking the project, due to the limitations imposed by GitHub, you need to
If you want to update instantly, you can check out the [GitHub documentation](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork) to learn how to synchronize a forked project with upstream code. If you want to update instantly, you can check out the [GitHub documentation](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork) to learn how to synchronize a forked project with upstream code.
You can star or watch this project or follow author to get release notifictions in time. You can star or watch this project or follow author to get release notifications in time.
## Access Password ## Access Password

View File

@ -43,6 +43,8 @@ export async function requestOpenai(req: NextRequest) {
}, },
method: req.method, method: req.method,
body: req.body, body: req.body,
// to fix #2485: https://stackoverflow.com/questions/55920957/cloudflare-worker-typeerror-one-time-use-body
redirect: "manual",
// @ts-ignore // @ts-ignore
duplex: "half", duplex: "half",
signal: controller.signal, signal: controller.signal,

View File

@ -178,7 +178,7 @@ export class ChatGPTApi implements LLMApi {
options.onFinish(message); options.onFinish(message);
} }
} catch (e) { } catch (e) {
console.log("[Request] failed to make a chat reqeust", e); console.log("[Request] failed to make a chat request", e);
options.onError?.(e as Error); options.onError?.(e as Error);
} }
} }

View File

@ -14,10 +14,11 @@
padding: 4px 10px; padding: 4px 10px;
animation: slide-in ease 0.3s; animation: slide-in ease 0.3s;
box-shadow: var(--card-shadow); box-shadow: var(--card-shadow);
transition: all ease 0.3s; transition: width ease 0.3s;
align-items: center; align-items: center;
height: 16px; height: 16px;
width: var(--icon-width); width: var(--icon-width);
overflow: hidden;
&:not(:last-child) { &:not(:last-child) {
margin-right: 5px; margin-right: 5px;
@ -29,14 +30,16 @@
opacity: 0; opacity: 0;
transform: translateX(-5px); transform: translateX(-5px);
transition: all ease 0.3s; transition: all ease 0.3s;
transition-delay: 0.1s;
pointer-events: none; pointer-events: none;
} }
&:hover { &:hover {
--delay: 0.5s;
width: var(--full-width); width: var(--full-width);
transition-delay: var(--delay);
.text { .text {
transition-delay: var(--delay);
opacity: 1; opacity: 1;
transform: translate(0); transform: translate(0);
} }

View File

@ -74,7 +74,13 @@ import {
showToast, showToast,
} from "./ui-lib"; } 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 {
CHAT_PAGE_SIZE,
LAST_INPUT_KEY,
MAX_RENDER_MSG_COUNT,
Path,
REQUEST_TIMEOUT_MS,
} from "../constant";
import { Avatar } from "./emoji"; import { Avatar } from "./emoji";
import { ContextPrompts, MaskAvatar, MaskConfig } from "./mask"; import { ContextPrompts, MaskAvatar, MaskConfig } from "./mask";
import { useMaskStore } from "../store/mask"; import { useMaskStore } from "../store/mask";
@ -371,23 +377,29 @@ function useScrollToBottom() {
// for auto-scroll // for auto-scroll
const scrollRef = useRef<HTMLDivElement>(null); const scrollRef = useRef<HTMLDivElement>(null);
const [autoScroll, setAutoScroll] = useState(true); const [autoScroll, setAutoScroll] = useState(true);
const scrollToBottom = useCallback(() => {
function scrollDomToBottom() {
const dom = scrollRef.current; const dom = scrollRef.current;
if (dom) { if (dom) {
requestAnimationFrame(() => dom.scrollTo(0, dom.scrollHeight)); requestAnimationFrame(() => {
setAutoScroll(true);
dom.scrollTo(0, dom.scrollHeight);
});
}
} }
}, []);
// auto scroll // auto scroll
useEffect(() => { useEffect(() => {
autoScroll && scrollToBottom(); if (autoScroll) {
scrollDomToBottom();
}
}); });
return { return {
scrollRef, scrollRef,
autoScroll, autoScroll,
setAutoScroll, setAutoScroll,
scrollToBottom, scrollDomToBottom,
}; };
} }
@ -504,6 +516,7 @@ export function ChatActions(props: {
{showModelSelector && ( {showModelSelector && (
<Selector <Selector
defaultSelectedValue={currentModel}
items={models.map((m) => ({ items={models.map((m) => ({
title: m, title: m,
value: m, value: m,
@ -531,7 +544,7 @@ export function EditMessageModal(props: { onClose: () => void }) {
return ( return (
<div className="modal-mask"> <div className="modal-mask">
<Modal <Modal
title={Locale.UI.Edit} title={Locale.Chat.EditMessage.Title}
onClose={props.onClose} onClose={props.onClose}
actions={[ actions={[
<IconButton <IconButton
@ -585,14 +598,11 @@ export function EditMessageModal(props: { onClose: () => void }) {
); );
} }
export function Chat() { function _Chat() {
type RenderMessage = ChatMessage & { preview?: boolean }; type RenderMessage = ChatMessage & { preview?: boolean };
const chatStore = useChatStore(); const chatStore = useChatStore();
const [session, sessionIndex] = useChatStore((state) => [ const session = chatStore.currentSession();
state.currentSession(),
state.currentSessionIndex,
]);
const config = useAppConfig(); const config = useAppConfig();
const fontSize = config.fontSize; const fontSize = config.fontSize;
@ -602,16 +612,11 @@ export function Chat() {
const [userInput, setUserInput] = useState(""); const [userInput, setUserInput] = 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, scrollDomToBottom } = useScrollToBottom();
const [hitBottom, setHitBottom] = useState(true); const [hitBottom, setHitBottom] = useState(true);
const isMobileScreen = useMobileScreen(); const isMobileScreen = useMobileScreen();
const navigate = useNavigate(); const navigate = useNavigate();
const onChatBodyScroll = (e: HTMLElement) => {
const isTouchBottom = e.scrollTop + e.clientHeight >= e.scrollHeight - 10;
setHitBottom(isTouchBottom);
};
// prompt hints // prompt hints
const promptStore = usePromptStore(); const promptStore = usePromptStore();
const [promptHints, setPromptHints] = useState<RenderPompt[]>([]); const [promptHints, setPromptHints] = useState<RenderPompt[]>([]);
@ -853,10 +858,9 @@ export function Chat() {
}); });
}; };
const context: RenderMessage[] = session.mask.hideContext const context: RenderMessage[] = useMemo(() => {
? [] return session.mask.hideContext ? [] : session.mask.context.slice();
: session.mask.context.slice(); }, [session.mask.context, session.mask.hideContext]);
const accessStore = useAccessStore(); const accessStore = useAccessStore();
if ( if (
@ -870,14 +874,9 @@ export function Chat() {
context.push(copiedHello); context.push(copiedHello);
} }
// clear context index = context length + index in messages
const clearContextIndex =
(session.clearContextIndex ?? -1) >= 0
? session.clearContextIndex! + context.length
: -1;
// preview messages // preview messages
const messages = context const renderMessages = useMemo(() => {
return context
.concat(session.messages as RenderMessage[]) .concat(session.messages as RenderMessage[])
.concat( .concat(
isLoading isLoading
@ -905,15 +904,68 @@ export function Chat() {
] ]
: [], : [],
); );
}, [
config.sendPreviewBubble,
context,
isLoading,
session.messages,
userInput,
]);
const [msgRenderIndex, _setMsgRenderIndex] = useState(
Math.max(0, renderMessages.length - CHAT_PAGE_SIZE),
);
function setMsgRenderIndex(newIndex: number) {
newIndex = Math.min(renderMessages.length - CHAT_PAGE_SIZE, newIndex);
newIndex = Math.max(0, newIndex);
_setMsgRenderIndex(newIndex);
}
const messages = useMemo(() => {
const endRenderIndex = Math.min(
msgRenderIndex + 3 * CHAT_PAGE_SIZE,
renderMessages.length,
);
return renderMessages.slice(msgRenderIndex, endRenderIndex);
}, [msgRenderIndex, renderMessages]);
const onChatBodyScroll = (e: HTMLElement) => {
const bottomHeight = e.scrollTop + e.clientHeight;
const edgeThreshold = e.clientHeight;
const isTouchTopEdge = e.scrollTop <= edgeThreshold;
const isTouchBottomEdge = bottomHeight >= e.scrollHeight - edgeThreshold;
const isHitBottom = bottomHeight >= e.scrollHeight - 10;
const prevPageMsgIndex = msgRenderIndex - CHAT_PAGE_SIZE;
const nextPageMsgIndex = msgRenderIndex + CHAT_PAGE_SIZE;
if (isTouchTopEdge) {
setMsgRenderIndex(prevPageMsgIndex);
} else if (isTouchBottomEdge) {
setMsgRenderIndex(nextPageMsgIndex);
}
setHitBottom(isHitBottom);
setAutoScroll(isHitBottom);
};
function scrollToBottom() {
setMsgRenderIndex(renderMessages.length - CHAT_PAGE_SIZE);
scrollDomToBottom();
}
// clear context index = context length + index in messages
const clearContextIndex =
(session.clearContextIndex ?? -1) >= 0
? session.clearContextIndex! + context.length - msgRenderIndex
: -1;
const [showPromptModal, setShowPromptModal] = useState(false); const [showPromptModal, setShowPromptModal] = useState(false);
const clientConfig = useMemo(() => getClientConfig(), []); const clientConfig = useMemo(() => getClientConfig(), []);
const location = useLocation(); const autoFocus = !isMobileScreen; // wont auto focus on mobile screen
const isChat = location.pathname === Path.Chat;
const autoFocus = !isMobileScreen || isChat; // only focus in chat page
const showMaxIcon = !isMobileScreen && !clientConfig?.isApp; const showMaxIcon = !isMobileScreen && !clientConfig?.isApp;
useCommand({ useCommand({
@ -1035,7 +1087,6 @@ export function Chat() {
ref={scrollRef} ref={scrollRef}
onScroll={(e) => onChatBodyScroll(e.currentTarget)} onScroll={(e) => onChatBodyScroll(e.currentTarget)}
onMouseDown={() => inputRef.current?.blur()} onMouseDown={() => inputRef.current?.blur()}
onWheel={(e) => setAutoScroll(hitBottom && e.deltaY > 0)}
onTouchStart={() => { onTouchStart={() => {
inputRef.current?.blur(); inputRef.current?.blur();
setAutoScroll(false); setAutoScroll(false);
@ -1053,7 +1104,7 @@ export function Chat() {
const shouldShowClearContextDivider = i === clearContextIndex - 1; const shouldShowClearContextDivider = i === clearContextIndex - 1;
return ( return (
<Fragment key={i}> <Fragment key={message.id}>
<div <div
className={ className={
isUser ? styles["chat-message-user"] : styles["chat-message"] isUser ? styles["chat-message-user"] : styles["chat-message"]
@ -1137,7 +1188,8 @@ export function Chat() {
<Markdown <Markdown
content={message.content} content={message.content}
loading={ loading={
(message.preview || message.content.length === 0) && (message.preview || message.streaming) &&
message.content.length === 0 &&
!isUser !isUser
} }
onContextMenu={(e) => onRightClick(e, message)} onContextMenu={(e) => onRightClick(e, message)}
@ -1147,7 +1199,7 @@ export function Chat() {
}} }}
fontSize={fontSize} fontSize={fontSize}
parentRef={scrollRef} parentRef={scrollRef}
defaultShow={i >= messages.length - 10} defaultShow={i >= messages.length - 6}
/> />
</div> </div>
@ -1191,8 +1243,8 @@ export function Chat() {
onInput={(e) => onInput(e.currentTarget.value)} onInput={(e) => onInput(e.currentTarget.value)}
value={userInput} value={userInput}
onKeyDown={onInputKeyDown} onKeyDown={onInputKeyDown}
onFocus={() => setAutoScroll(true)} onFocus={scrollToBottom}
onBlur={() => setAutoScroll(false)} onClick={scrollToBottom}
rows={inputRows} rows={inputRows}
autoFocus={autoFocus} autoFocus={autoFocus}
style={{ style={{
@ -1223,3 +1275,9 @@ export function Chat() {
</div> </div>
); );
} }
export function Chat() {
const chatStore = useChatStore();
const sessionIndex = chatStore.currentSessionIndex;
return <_Chat key={sessionIndex}></_Chat>;
}

View File

@ -174,6 +174,7 @@
user-select: none; user-select: none;
border: 2px solid transparent; border: 2px solid transparent;
position: relative; position: relative;
content-visibility: auto;
} }
.chat-item:hover { .chat-item:hover {

View File

@ -104,8 +104,7 @@ const loadAsyncGoogleFont = () => {
getClientConfig()?.buildMode === "export" ? remoteFontUrl : proxyFontUrl; getClientConfig()?.buildMode === "export" ? remoteFontUrl : proxyFontUrl;
linkEl.rel = "stylesheet"; linkEl.rel = "stylesheet";
linkEl.href = linkEl.href =
googleFontUrl + googleFontUrl + "/css2?family=Noto+Sans:wght@300;400;700;900&display=swap";
"/css2?family=Noto+Sans+SC:wght@300;400;700;900&display=swap";
document.head.appendChild(linkEl); document.head.appendChild(linkEl);
}; };

View File

@ -146,70 +146,23 @@ export function Markdown(
} & React.DOMAttributes<HTMLDivElement>, } & React.DOMAttributes<HTMLDivElement>,
) { ) {
const mdRef = useRef<HTMLDivElement>(null); const mdRef = useRef<HTMLDivElement>(null);
const renderedHeight = useRef(0);
const renderedWidth = useRef(0);
const inView = useRef(!!props.defaultShow);
const [_, triggerRender] = useState(0);
const checkInView = useThrottledCallback(
() => {
const parent = props.parentRef?.current;
const md = mdRef.current;
if (parent && md && !props.defaultShow) {
const parentBounds = parent.getBoundingClientRect();
const twoScreenHeight = Math.max(500, parentBounds.height * 2);
const mdBounds = md.getBoundingClientRect();
const parentTop = parentBounds.top - twoScreenHeight;
const parentBottom = parentBounds.bottom + twoScreenHeight;
const isOverlap =
Math.max(parentTop, mdBounds.top) <=
Math.min(parentBottom, mdBounds.bottom);
inView.current = isOverlap;
triggerRender(Date.now());
}
if (inView.current && md) {
const rect = md.getBoundingClientRect();
renderedHeight.current = Math.max(renderedHeight.current, rect.height);
renderedWidth.current = Math.max(renderedWidth.current, rect.width);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
},
300,
{
leading: true,
trailing: true,
},
);
useEffect(() => {
props.parentRef?.current?.addEventListener("scroll", checkInView);
checkInView();
return () =>
props.parentRef?.current?.removeEventListener("scroll", checkInView);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const getSize = (x: number) => (!inView.current && x > 0 ? x : "auto");
return ( return (
<div <div
className="markdown-body" className="markdown-body"
style={{ style={{
fontSize: `${props.fontSize ?? 14}px`, fontSize: `${props.fontSize ?? 14}px`,
height: getSize(renderedHeight.current),
width: getSize(renderedWidth.current),
direction: /[\u0600-\u06FF]/.test(props.content) ? "rtl" : "ltr", direction: /[\u0600-\u06FF]/.test(props.content) ? "rtl" : "ltr",
}} }}
ref={mdRef} ref={mdRef}
onContextMenu={props.onContextMenu} onContextMenu={props.onContextMenu}
onDoubleClickCapture={props.onDoubleClickCapture} onDoubleClickCapture={props.onDoubleClickCapture}
> >
{inView.current && {props.loading ? (
(props.loading ? (
<LoadingIcon /> <LoadingIcon />
) : ( ) : (
<MarkdownContent content={props.content} /> <MarkdownContent content={props.content} />
))} )}
</div> </div>
); );
} }

View File

@ -76,7 +76,7 @@ export function ModelConfigList(props: {
<input <input
type="number" type="number"
min={100} min={100}
max={32000} max={100000}
value={props.modelConfig.max_tokens} value={props.modelConfig.max_tokens}
onChange={(e) => onChange={(e) =>
props.updateConfig( props.updateConfig(
@ -169,7 +169,7 @@ export function ModelConfigList(props: {
title={props.modelConfig.historyMessageCount.toString()} title={props.modelConfig.historyMessageCount.toString()}
value={props.modelConfig.historyMessageCount} value={props.modelConfig.historyMessageCount}
min="0" min="0"
max="32" max="64"
step="1" step="1"
onChange={(e) => onChange={(e) =>
props.updateConfig( props.updateConfig(

View File

@ -377,7 +377,7 @@ export function showPrompt(content: any, value = "", rows = 3) {
}; };
return new Promise<string>((resolve) => { return new Promise<string>((resolve) => {
let userInput = ""; let userInput = value;
root.render( root.render(
<Modal <Modal
@ -443,6 +443,7 @@ export function Selector<T>(props: {
subTitle?: string; subTitle?: string;
value: T; value: T;
}>; }>;
defaultSelectedValue?: T;
onSelection?: (selection: T[]) => void; onSelection?: (selection: T[]) => void;
onClose?: () => void; onClose?: () => void;
multiple?: boolean; multiple?: boolean;
@ -452,6 +453,7 @@ export function Selector<T>(props: {
<div className={styles["selector-content"]}> <div className={styles["selector-content"]}>
<List> <List>
{props.items.map((item, i) => { {props.items.map((item, i) => {
const selected = props.defaultSelectedValue === item.value;
return ( return (
<ListItem <ListItem
className={styles["selector-item"]} className={styles["selector-item"]}
@ -462,7 +464,20 @@ export function Selector<T>(props: {
props.onSelection?.([item.value]); props.onSelection?.([item.value]);
props.onClose?.(); props.onClose?.();
}} }}
></ListItem> >
{selected ? (
<div
style={{
height: 10,
width: 10,
backgroundColor: "var(--primary)",
borderRadius: 10,
}}
></div>
) : (
<></>
)}
</ListItem>
); );
})} })}
</List> </List>

View File

@ -109,3 +109,6 @@ export const DEFAULT_MODELS = [
available: true, available: true,
}, },
] as const; ] as const;
export const CHAT_PAGE_SIZE = 15;
export const MAX_RENDER_MSG_COUNT = 45;

View File

@ -19,6 +19,7 @@ const cn = {
Chat: { Chat: {
SubTitle: (count: number) => `与 SoulShellGPT 的 ${count} 条对话`, SubTitle: (count: number) => `与 SoulShellGPT 的 ${count} 条对话`,
EditMessage: { EditMessage: {
Title: "编辑消息记录",
Topic: { Topic: {
Title: "聊天主题", Title: "聊天主题",
SubTitle: "更改当前聊天主题", SubTitle: "更改当前聊天主题",
@ -274,7 +275,7 @@ const cn = {
Context: { Context: {
Toast: (x: any) => `包含 ${x} 条预设提示词`, Toast: (x: any) => `包含 ${x} 条预设提示词`,
Edit: "当前对话设置", Edit: "当前对话设置",
Add: "新增预设对话", Add: "新增一条对话",
Clear: "上下文已清除", Clear: "上下文已清除",
Revert: "恢复上下文", Revert: "恢复上下文",
}, },

View File

@ -5,7 +5,7 @@ const cs: PartialLocaleType = {
WIP: "V přípravě...", WIP: "V přípravě...",
Error: { Error: {
Unauthorized: Unauthorized:
"Neoprávněný přístup, zadejte přístupový kód na stránce nastavení.", "Neoprávněný přístup, zadejte přístupový kód na [stránce](/#/auth) nastavení.",
}, },
ChatItem: { ChatItem: {
ChatItemCount: (count: number) => `${count} zpráv`, ChatItemCount: (count: number) => `${count} zpráv`,

View File

@ -5,7 +5,7 @@ const de: PartialLocaleType = {
WIP: "In Bearbeitung...", WIP: "In Bearbeitung...",
Error: { Error: {
Unauthorized: Unauthorized:
"Unbefugter Zugriff, bitte geben Sie den Zugangscode auf der Einstellungsseite ein.", "Unbefugter Zugriff, bitte geben Sie den Zugangscode auf der [Einstellungsseite](/#/auth) ein.",
}, },
ChatItem: { ChatItem: {
ChatItemCount: (count: number) => `${count} Nachrichten`, ChatItemCount: (count: number) => `${count} Nachrichten`,

View File

@ -21,6 +21,7 @@ const en: LocaleType = {
Chat: { Chat: {
SubTitle: (count: number) => `${count} messages with SoulShellGPT`, SubTitle: (count: number) => `${count} messages with SoulShellGPT`,
EditMessage: { EditMessage: {
Title: "Edit All Messages",
Topic: { Topic: {
Title: "Topic", Title: "Topic",
SubTitle: "Change the current topic", SubTitle: "Change the current topic",

View File

@ -5,7 +5,7 @@ const es: PartialLocaleType = {
WIP: "En construcción...", WIP: "En construcción...",
Error: { Error: {
Unauthorized: Unauthorized:
"Acceso no autorizado, por favor ingrese el código de acceso en la página de configuración.", "Acceso no autorizado, por favor ingrese el código de acceso en la [página](/#/auth) de configuración.",
}, },
ChatItem: { ChatItem: {
ChatItemCount: (count: number) => `${count} mensajes`, ChatItemCount: (count: number) => `${count} mensajes`,

View File

@ -5,7 +5,7 @@ const fr: PartialLocaleType = {
WIP: "Prochainement...", WIP: "Prochainement...",
Error: { Error: {
Unauthorized: Unauthorized:
"Accès non autorisé, veuillez saisir le code d'accès dans la page des paramètres.", "Accès non autorisé, veuillez saisir le code d'accès dans la [page](/#/auth) des paramètres.",
}, },
ChatItem: { ChatItem: {
ChatItemCount: (count: number) => `${count} messages en total`, ChatItemCount: (count: number) => `${count} messages en total`,

View File

@ -5,7 +5,7 @@ const it: PartialLocaleType = {
WIP: "Work in progress...", WIP: "Work in progress...",
Error: { Error: {
Unauthorized: Unauthorized:
"Accesso non autorizzato, inserire il codice di accesso nella pagina delle impostazioni.", "Accesso non autorizzato, inserire il codice di accesso nella [pagina](/#/auth) delle impostazioni.",
}, },
ChatItem: { ChatItem: {
ChatItemCount: (count: number) => `${count} messaggi`, ChatItemCount: (count: number) => `${count} messaggi`,

View File

@ -5,7 +5,8 @@ import type { PartialLocaleType } from "./index";
const ko: PartialLocaleType = { const ko: PartialLocaleType = {
WIP: "곧 출시 예정...", WIP: "곧 출시 예정...",
Error: { Error: {
Unauthorized: "권한이 없습니다. 설정 페이지에서 액세스 코드를 입력하세요.", Unauthorized:
"권한이 없습니다. 설정 페이지에서 액세스 코드를 [입력하세요](/#/auth).",
}, },
ChatItem: { ChatItem: {
ChatItemCount: (count: number) => `${count}개의 메시지`, ChatItemCount: (count: number) => `${count}개의 메시지`,

View File

@ -4,7 +4,8 @@ import type { PartialLocaleType } from "./index";
const no: PartialLocaleType = { const no: PartialLocaleType = {
WIP: "Arbeid pågår ...", WIP: "Arbeid pågår ...",
Error: { Error: {
Unauthorized: "Du har ikke tilgang. Vennlig oppgi tildelt adgangskode.", Unauthorized:
"Du har ikke tilgang. [Vennlig oppgi tildelt adgangskode](/#/auth).",
}, },
ChatItem: { ChatItem: {
ChatItemCount: (count: number) => `${count} meldinger`, ChatItemCount: (count: number) => `${count} meldinger`,

View File

@ -5,7 +5,7 @@ const ru: PartialLocaleType = {
WIP: "Скоро...", WIP: "Скоро...",
Error: { Error: {
Unauthorized: Unauthorized:
"Несанкционированный доступ. Пожалуйста, введите код доступа на странице настроек.", "Несанкционированный доступ. Пожалуйста, введите код доступа на [странице](/#/auth) настроек.",
}, },
ChatItem: { ChatItem: {
ChatItemCount: (count: number) => `${count} сообщений`, ChatItemCount: (count: number) => `${count} сообщений`,

View File

@ -5,7 +5,7 @@ const tr: PartialLocaleType = {
WIP: "Çalışma devam ediyor...", WIP: "Çalışma devam ediyor...",
Error: { Error: {
Unauthorized: Unauthorized:
"Yetkisiz erişim, lütfen erişim kodunu ayarlar sayfasından giriniz.", "Yetkisiz erişim, lütfen erişim kodunu ayarlar [sayfasından](/#/auth) giriniz.",
}, },
ChatItem: { ChatItem: {
ChatItemCount: (count: number) => `${count} mesaj`, ChatItemCount: (count: number) => `${count} mesaj`,

View File

@ -4,7 +4,7 @@ import type { PartialLocaleType } from "./index";
const tw: PartialLocaleType = { const tw: PartialLocaleType = {
WIP: "該功能仍在開發中……", WIP: "該功能仍在開發中……",
Error: { Error: {
Unauthorized: "目前您的狀態是未授權,請前往設定頁面輸入授權碼。", Unauthorized: "目前您的狀態是未授權,請前往[設定頁面](/#/auth)輸入授權碼。",
}, },
ChatItem: { ChatItem: {
ChatItemCount: (count: number) => `${count} 條對話`, ChatItemCount: (count: number) => `${count} 條對話`,

View File

@ -332,7 +332,7 @@ export const useChatStore = create<ChatStore>()(
}, },
onError(error) { onError(error) {
const isAborted = error.message.includes("aborted"); const isAborted = error.message.includes("aborted");
botMessage.content = botMessage.content +=
"\n\n" + "\n\n" +
prettyObject({ prettyObject({
error: true, error: true,
@ -553,7 +553,7 @@ export const useChatStore = create<ChatStore>()(
date: "", date: "",
}), }),
), ),
config: { ...modelConfig, stream: true }, config: { ...modelConfig, stream: true, model: "gpt-3.5-turbo" },
onUpdate(message) { onUpdate(message) {
session.memoryPrompt = message; session.memoryPrompt = message;
}, },

View File

@ -81,7 +81,7 @@ export const ModalConfigValidator = {
return x as ModelType; return x as ModelType;
}, },
max_tokens(x: number) { max_tokens(x: number) {
return limitNumber(x, 0, 32000, 2000); return limitNumber(x, 0, 100000, 2000);
}, },
presence_penalty(x: number) { presence_penalty(x: number) {
return limitNumber(x, -2, 2, 0); return limitNumber(x, -2, 2, 0);

View File

@ -89,7 +89,7 @@
html { html {
height: var(--full-height); height: var(--full-height);
font-family: "Noto Sans SC", "SF Pro SC", "SF Pro Text", "SF Pro Icons", font-family: "Noto Sans", "SF Pro SC", "SF Pro Text", "SF Pro Icons",
"PingFang SC", "Helvetica Neue", "Helvetica", "Arial", sans-serif; "PingFang SC", "Helvetica Neue", "Helvetica", "Arial", sans-serif;
} }

View File

@ -39,7 +39,7 @@ Docker 版本相当于稳定版latest Docker 总是与 latest release version
> 相关讨论:[#386](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/386) > 相关讨论:[#386](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/386)
如果你使用 ngnix 反向代理,需要在配置文件中增加下列代码: 如果你使用 nginx 反向代理,需要在配置文件中增加下列代码:
``` ```
# 不缓存,支持流式输出 # 不缓存,支持流式输出

View File

@ -39,7 +39,7 @@ Esta es su contraseña de acceso personalizada, puede elegir:
> Debates relacionados:[#386](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/386) > Debates relacionados:[#386](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/386)
Si utiliza el proxy inverso ngnix, debe agregar el siguiente código al archivo de configuración: Si utiliza el proxy inverso nginx, debe agregar el siguiente código al archivo de configuración:
# 不缓存,支持流式输出 # 不缓存,支持流式输出
proxy_cache off; # 关闭缓存 proxy_cache off; # 关闭缓存

View File

@ -45,7 +45,7 @@
"@tauri-apps/cli": "^1.4.0", "@tauri-apps/cli": "^1.4.0",
"@types/node": "^20.3.3", "@types/node": "^20.3.3",
"@types/react": "^18.2.14", "@types/react": "^18.2.14",
"@types/react-dom": "^18.0.11", "@types/react-dom": "^18.2.7",
"@types/react-katex": "^3.0.0", "@types/react-katex": "^3.0.0",
"@types/spark-md5": "^3.0.2", "@types/spark-md5": "^3.0.2",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",

View File

@ -9,7 +9,7 @@
}, },
"package": { "package": {
"productName": "ChatGPT Next Web", "productName": "ChatGPT Next Web",
"version": "2.9.1" "version": "2.9.2"
}, },
"tauri": { "tauri": {
"allowlist": { "allowlist": {

View File

@ -1505,10 +1505,10 @@
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf" resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf"
integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w== integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==
"@types/react-dom@^18.0.11": "@types/react-dom@^18.2.7":
version "18.0.11" version "18.2.7"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.0.11.tgz#321351c1459bc9ca3d216aefc8a167beec334e33" resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.7.tgz#67222a08c0a6ae0a0da33c3532348277c70abb63"
integrity sha512-O38bPbI2CWtgw/OoQoY+BRelw7uysmXbWvw3nLWO21H1HSh+GOlqPuXshJfjmpNlKiiSDG9cc1JZAaMmVdcTlw== integrity sha512-GRaAEriuT4zp9N4p1i8BDBYmEyfo+xQ3yHjJU4eiK5NDa1RmUZG+unZABUTK4/Ox/M+GaHwb6Ow8rUITrtjszA==
dependencies: dependencies:
"@types/react" "*" "@types/react" "*"