mirror of
https://github.com/Yidadaa/ChatGPT-Next-Web.git
synced 2025-09-01 12:46:58 +08:00
Compare commits
39 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
9834a67cbd | ||
|
d85a4a0c9f | ||
|
67c8ec6d7e | ||
|
2a2dd7ea19 | ||
|
153e7ac7e4 | ||
|
9420fd4946 | ||
|
b14c5cd89c | ||
|
4ab9141429 | ||
|
769c2f9f49 | ||
|
c41c498a9c | ||
|
d1096582a5 | ||
|
543989151f | ||
|
7da83987e4 | ||
|
523d553dac | ||
|
ff6f0e9546 | ||
|
3e63f6ba34 | ||
|
811b92d24e | ||
|
bc5ddc4541 | ||
|
44e43729bf | ||
|
203067c936 | ||
|
f3b508c088 | ||
|
081d84f848 | ||
|
e381add944 | ||
|
75d4eca722 | ||
|
56904ad6b2 | ||
|
b5ef552c25 | ||
|
cbabb9392c | ||
|
531b3dcf9e | ||
|
d975daf3f0 | ||
|
6137d551fe | ||
|
cf625e3542 | ||
|
e354fca4a4 | ||
|
129e7afc16 | ||
|
e83e0f6a33 | ||
|
cf4f928b25 | ||
|
e4d955e3f9 | ||
|
13576087f4 | ||
|
e74f6f3183 | ||
|
8302d1d3c1 |
@@ -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.
|
||||
|
||||
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
|
||||
|
||||
|
@@ -43,6 +43,8 @@ export async function requestOpenai(req: NextRequest) {
|
||||
},
|
||||
method: req.method,
|
||||
body: req.body,
|
||||
// to fix #2485: https://stackoverflow.com/questions/55920957/cloudflare-worker-typeerror-one-time-use-body
|
||||
redirect: "manual",
|
||||
// @ts-ignore
|
||||
duplex: "half",
|
||||
signal: controller.signal,
|
||||
|
@@ -178,7 +178,7 @@ export class ChatGPTApi implements LLMApi {
|
||||
options.onFinish(message);
|
||||
}
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
|
@@ -14,10 +14,11 @@
|
||||
padding: 4px 10px;
|
||||
animation: slide-in ease 0.3s;
|
||||
box-shadow: var(--card-shadow);
|
||||
transition: all ease 0.3s;
|
||||
transition: width ease 0.3s;
|
||||
align-items: center;
|
||||
height: 16px;
|
||||
width: var(--icon-width);
|
||||
overflow: hidden;
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-right: 5px;
|
||||
@@ -29,14 +30,16 @@
|
||||
opacity: 0;
|
||||
transform: translateX(-5px);
|
||||
transition: all ease 0.3s;
|
||||
transition-delay: 0.1s;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
--delay: 0.5s;
|
||||
width: var(--full-width);
|
||||
transition-delay: var(--delay);
|
||||
|
||||
.text {
|
||||
transition-delay: var(--delay);
|
||||
opacity: 1;
|
||||
transform: translate(0);
|
||||
}
|
||||
|
@@ -74,7 +74,13 @@ import {
|
||||
showToast,
|
||||
} from "./ui-lib";
|
||||
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 { ContextPrompts, MaskAvatar, MaskConfig } from "./mask";
|
||||
import { useMaskStore } from "../store/mask";
|
||||
@@ -371,23 +377,29 @@ function useScrollToBottom() {
|
||||
// for auto-scroll
|
||||
const scrollRef = useRef<HTMLDivElement>(null);
|
||||
const [autoScroll, setAutoScroll] = useState(true);
|
||||
const scrollToBottom = useCallback(() => {
|
||||
|
||||
function scrollDomToBottom() {
|
||||
const dom = scrollRef.current;
|
||||
if (dom) {
|
||||
requestAnimationFrame(() => dom.scrollTo(0, dom.scrollHeight));
|
||||
requestAnimationFrame(() => {
|
||||
setAutoScroll(true);
|
||||
dom.scrollTo(0, dom.scrollHeight);
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
}
|
||||
|
||||
// auto scroll
|
||||
useEffect(() => {
|
||||
autoScroll && scrollToBottom();
|
||||
if (autoScroll) {
|
||||
scrollDomToBottom();
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
scrollRef,
|
||||
autoScroll,
|
||||
setAutoScroll,
|
||||
scrollToBottom,
|
||||
scrollDomToBottom,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -504,6 +516,7 @@ export function ChatActions(props: {
|
||||
|
||||
{showModelSelector && (
|
||||
<Selector
|
||||
defaultSelectedValue={currentModel}
|
||||
items={models.map((m) => ({
|
||||
title: m,
|
||||
value: m,
|
||||
@@ -531,7 +544,7 @@ export function EditMessageModal(props: { onClose: () => void }) {
|
||||
return (
|
||||
<div className="modal-mask">
|
||||
<Modal
|
||||
title={Locale.UI.Edit}
|
||||
title={Locale.Chat.EditMessage.Title}
|
||||
onClose={props.onClose}
|
||||
actions={[
|
||||
<IconButton
|
||||
@@ -585,14 +598,11 @@ export function EditMessageModal(props: { onClose: () => void }) {
|
||||
);
|
||||
}
|
||||
|
||||
export function Chat() {
|
||||
function _Chat() {
|
||||
type RenderMessage = ChatMessage & { preview?: boolean };
|
||||
|
||||
const chatStore = useChatStore();
|
||||
const [session, sessionIndex] = useChatStore((state) => [
|
||||
state.currentSession(),
|
||||
state.currentSessionIndex,
|
||||
]);
|
||||
const session = chatStore.currentSession();
|
||||
const config = useAppConfig();
|
||||
const fontSize = config.fontSize;
|
||||
|
||||
@@ -602,16 +612,11 @@ export function Chat() {
|
||||
const [userInput, setUserInput] = useState("");
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const { submitKey, shouldSubmit } = useSubmitHandler();
|
||||
const { scrollRef, setAutoScroll, scrollToBottom } = useScrollToBottom();
|
||||
const { scrollRef, setAutoScroll, scrollDomToBottom } = useScrollToBottom();
|
||||
const [hitBottom, setHitBottom] = useState(true);
|
||||
const isMobileScreen = useMobileScreen();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const onChatBodyScroll = (e: HTMLElement) => {
|
||||
const isTouchBottom = e.scrollTop + e.clientHeight >= e.scrollHeight - 10;
|
||||
setHitBottom(isTouchBottom);
|
||||
};
|
||||
|
||||
// prompt hints
|
||||
const promptStore = usePromptStore();
|
||||
const [promptHints, setPromptHints] = useState<RenderPompt[]>([]);
|
||||
@@ -853,10 +858,9 @@ export function Chat() {
|
||||
});
|
||||
};
|
||||
|
||||
const context: RenderMessage[] = session.mask.hideContext
|
||||
? []
|
||||
: session.mask.context.slice();
|
||||
|
||||
const context: RenderMessage[] = useMemo(() => {
|
||||
return session.mask.hideContext ? [] : session.mask.context.slice();
|
||||
}, [session.mask.context, session.mask.hideContext]);
|
||||
const accessStore = useAccessStore();
|
||||
|
||||
if (
|
||||
@@ -870,50 +874,98 @@ export function Chat() {
|
||||
context.push(copiedHello);
|
||||
}
|
||||
|
||||
// preview messages
|
||||
const renderMessages = useMemo(() => {
|
||||
return context
|
||||
.concat(session.messages as RenderMessage[])
|
||||
.concat(
|
||||
isLoading
|
||||
? [
|
||||
{
|
||||
...createMessage({
|
||||
role: "assistant",
|
||||
content: "……",
|
||||
}),
|
||||
preview: true,
|
||||
},
|
||||
]
|
||||
: [],
|
||||
)
|
||||
.concat(
|
||||
userInput.length > 0 && config.sendPreviewBubble
|
||||
? [
|
||||
{
|
||||
...createMessage({
|
||||
role: "user",
|
||||
content: userInput,
|
||||
}),
|
||||
preview: true,
|
||||
},
|
||||
]
|
||||
: [],
|
||||
);
|
||||
}, [
|
||||
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
|
||||
? session.clearContextIndex! + context.length - msgRenderIndex
|
||||
: -1;
|
||||
|
||||
// preview messages
|
||||
const messages = context
|
||||
.concat(session.messages as RenderMessage[])
|
||||
.concat(
|
||||
isLoading
|
||||
? [
|
||||
{
|
||||
...createMessage({
|
||||
role: "assistant",
|
||||
content: "……",
|
||||
}),
|
||||
preview: true,
|
||||
},
|
||||
]
|
||||
: [],
|
||||
)
|
||||
.concat(
|
||||
userInput.length > 0 && config.sendPreviewBubble
|
||||
? [
|
||||
{
|
||||
...createMessage({
|
||||
role: "user",
|
||||
content: userInput,
|
||||
}),
|
||||
preview: true,
|
||||
},
|
||||
]
|
||||
: [],
|
||||
);
|
||||
|
||||
const [showPromptModal, setShowPromptModal] = useState(false);
|
||||
|
||||
const clientConfig = useMemo(() => getClientConfig(), []);
|
||||
|
||||
const location = useLocation();
|
||||
const isChat = location.pathname === Path.Chat;
|
||||
|
||||
const autoFocus = !isMobileScreen || isChat; // only focus in chat page
|
||||
const autoFocus = !isMobileScreen; // wont auto focus on mobile screen
|
||||
const showMaxIcon = !isMobileScreen && !clientConfig?.isApp;
|
||||
|
||||
useCommand({
|
||||
@@ -1035,7 +1087,6 @@ export function Chat() {
|
||||
ref={scrollRef}
|
||||
onScroll={(e) => onChatBodyScroll(e.currentTarget)}
|
||||
onMouseDown={() => inputRef.current?.blur()}
|
||||
onWheel={(e) => setAutoScroll(hitBottom && e.deltaY > 0)}
|
||||
onTouchStart={() => {
|
||||
inputRef.current?.blur();
|
||||
setAutoScroll(false);
|
||||
@@ -1053,7 +1104,7 @@ export function Chat() {
|
||||
const shouldShowClearContextDivider = i === clearContextIndex - 1;
|
||||
|
||||
return (
|
||||
<Fragment key={i}>
|
||||
<Fragment key={message.id}>
|
||||
<div
|
||||
className={
|
||||
isUser ? styles["chat-message-user"] : styles["chat-message"]
|
||||
@@ -1137,7 +1188,8 @@ export function Chat() {
|
||||
<Markdown
|
||||
content={message.content}
|
||||
loading={
|
||||
(message.preview || message.content.length === 0) &&
|
||||
(message.preview || message.streaming) &&
|
||||
message.content.length === 0 &&
|
||||
!isUser
|
||||
}
|
||||
onContextMenu={(e) => onRightClick(e, message)}
|
||||
@@ -1147,7 +1199,7 @@ export function Chat() {
|
||||
}}
|
||||
fontSize={fontSize}
|
||||
parentRef={scrollRef}
|
||||
defaultShow={i >= messages.length - 10}
|
||||
defaultShow={i >= messages.length - 6}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1191,8 +1243,8 @@ export function Chat() {
|
||||
onInput={(e) => onInput(e.currentTarget.value)}
|
||||
value={userInput}
|
||||
onKeyDown={onInputKeyDown}
|
||||
onFocus={() => setAutoScroll(true)}
|
||||
onBlur={() => setAutoScroll(false)}
|
||||
onFocus={scrollToBottom}
|
||||
onClick={scrollToBottom}
|
||||
rows={inputRows}
|
||||
autoFocus={autoFocus}
|
||||
style={{
|
||||
@@ -1223,3 +1275,9 @@ export function Chat() {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function Chat() {
|
||||
const chatStore = useChatStore();
|
||||
const sessionIndex = chatStore.currentSessionIndex;
|
||||
return <_Chat key={sessionIndex}></_Chat>;
|
||||
}
|
||||
|
@@ -174,6 +174,7 @@
|
||||
user-select: none;
|
||||
border: 2px solid transparent;
|
||||
position: relative;
|
||||
content-visibility: auto;
|
||||
}
|
||||
|
||||
.chat-item:hover {
|
||||
|
@@ -104,8 +104,7 @@ const loadAsyncGoogleFont = () => {
|
||||
getClientConfig()?.buildMode === "export" ? remoteFontUrl : proxyFontUrl;
|
||||
linkEl.rel = "stylesheet";
|
||||
linkEl.href =
|
||||
googleFontUrl +
|
||||
"/css2?family=Noto+Sans+SC:wght@300;400;700;900&display=swap";
|
||||
googleFontUrl + "/css2?family=Noto+Sans:wght@300;400;700;900&display=swap";
|
||||
document.head.appendChild(linkEl);
|
||||
};
|
||||
|
||||
|
@@ -146,70 +146,23 @@ export function Markdown(
|
||||
} & React.DOMAttributes<HTMLDivElement>,
|
||||
) {
|
||||
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 (
|
||||
<div
|
||||
className="markdown-body"
|
||||
style={{
|
||||
fontSize: `${props.fontSize ?? 14}px`,
|
||||
height: getSize(renderedHeight.current),
|
||||
width: getSize(renderedWidth.current),
|
||||
direction: /[\u0600-\u06FF]/.test(props.content) ? "rtl" : "ltr",
|
||||
}}
|
||||
ref={mdRef}
|
||||
onContextMenu={props.onContextMenu}
|
||||
onDoubleClickCapture={props.onDoubleClickCapture}
|
||||
>
|
||||
{inView.current &&
|
||||
(props.loading ? (
|
||||
<LoadingIcon />
|
||||
) : (
|
||||
<MarkdownContent content={props.content} />
|
||||
))}
|
||||
{props.loading ? (
|
||||
<LoadingIcon />
|
||||
) : (
|
||||
<MarkdownContent content={props.content} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@@ -76,7 +76,7 @@ export function ModelConfigList(props: {
|
||||
<input
|
||||
type="number"
|
||||
min={100}
|
||||
max={32000}
|
||||
max={100000}
|
||||
value={props.modelConfig.max_tokens}
|
||||
onChange={(e) =>
|
||||
props.updateConfig(
|
||||
@@ -169,7 +169,7 @@ export function ModelConfigList(props: {
|
||||
title={props.modelConfig.historyMessageCount.toString()}
|
||||
value={props.modelConfig.historyMessageCount}
|
||||
min="0"
|
||||
max="32"
|
||||
max="64"
|
||||
step="1"
|
||||
onChange={(e) =>
|
||||
props.updateConfig(
|
||||
|
@@ -377,7 +377,7 @@ export function showPrompt(content: any, value = "", rows = 3) {
|
||||
};
|
||||
|
||||
return new Promise<string>((resolve) => {
|
||||
let userInput = "";
|
||||
let userInput = value;
|
||||
|
||||
root.render(
|
||||
<Modal
|
||||
@@ -443,6 +443,7 @@ export function Selector<T>(props: {
|
||||
subTitle?: string;
|
||||
value: T;
|
||||
}>;
|
||||
defaultSelectedValue?: T;
|
||||
onSelection?: (selection: T[]) => void;
|
||||
onClose?: () => void;
|
||||
multiple?: boolean;
|
||||
@@ -452,6 +453,7 @@ export function Selector<T>(props: {
|
||||
<div className={styles["selector-content"]}>
|
||||
<List>
|
||||
{props.items.map((item, i) => {
|
||||
const selected = props.defaultSelectedValue === item.value;
|
||||
return (
|
||||
<ListItem
|
||||
className={styles["selector-item"]}
|
||||
@@ -462,7 +464,20 @@ export function Selector<T>(props: {
|
||||
props.onSelection?.([item.value]);
|
||||
props.onClose?.();
|
||||
}}
|
||||
></ListItem>
|
||||
>
|
||||
{selected ? (
|
||||
<div
|
||||
style={{
|
||||
height: 10,
|
||||
width: 10,
|
||||
backgroundColor: "var(--primary)",
|
||||
borderRadius: 10,
|
||||
}}
|
||||
></div>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</ListItem>
|
||||
);
|
||||
})}
|
||||
</List>
|
||||
|
@@ -41,7 +41,7 @@ export const MAX_SIDEBAR_WIDTH = 500;
|
||||
export const MIN_SIDEBAR_WIDTH = 230;
|
||||
export const NARROW_SIDEBAR_WIDTH = 100;
|
||||
|
||||
export const ACCESS_CODE_PREFIX = "ak-";
|
||||
export const ACCESS_CODE_PREFIX = "nk-";
|
||||
|
||||
export const LAST_INPUT_KEY = "last-input";
|
||||
|
||||
@@ -109,3 +109,6 @@ export const DEFAULT_MODELS = [
|
||||
available: true,
|
||||
},
|
||||
] as const;
|
||||
|
||||
export const CHAT_PAGE_SIZE = 15;
|
||||
export const MAX_RENDER_MSG_COUNT = 45;
|
||||
|
@@ -19,6 +19,7 @@ const cn = {
|
||||
Chat: {
|
||||
SubTitle: (count: number) => `共 ${count} 条对话`,
|
||||
EditMessage: {
|
||||
Title: "编辑消息记录",
|
||||
Topic: {
|
||||
Title: "聊天主题",
|
||||
SubTitle: "更改当前聊天主题",
|
||||
@@ -274,7 +275,7 @@ const cn = {
|
||||
Context: {
|
||||
Toast: (x: any) => `包含 ${x} 条预设提示词`,
|
||||
Edit: "当前对话设置",
|
||||
Add: "新增预设对话",
|
||||
Add: "新增一条对话",
|
||||
Clear: "上下文已清除",
|
||||
Revert: "恢复上下文",
|
||||
},
|
||||
|
@@ -5,7 +5,7 @@ const cs: PartialLocaleType = {
|
||||
WIP: "V přípravě...",
|
||||
Error: {
|
||||
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: {
|
||||
ChatItemCount: (count: number) => `${count} zpráv`,
|
||||
|
@@ -5,7 +5,7 @@ const de: PartialLocaleType = {
|
||||
WIP: "In Bearbeitung...",
|
||||
Error: {
|
||||
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: {
|
||||
ChatItemCount: (count: number) => `${count} Nachrichten`,
|
||||
|
@@ -21,6 +21,7 @@ const en: LocaleType = {
|
||||
Chat: {
|
||||
SubTitle: (count: number) => `${count} messages`,
|
||||
EditMessage: {
|
||||
Title: "Edit All Messages",
|
||||
Topic: {
|
||||
Title: "Topic",
|
||||
SubTitle: "Change the current topic",
|
||||
|
@@ -5,7 +5,7 @@ const es: PartialLocaleType = {
|
||||
WIP: "En construcción...",
|
||||
Error: {
|
||||
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: {
|
||||
ChatItemCount: (count: number) => `${count} mensajes`,
|
||||
|
@@ -5,7 +5,7 @@ const fr: PartialLocaleType = {
|
||||
WIP: "Prochainement...",
|
||||
Error: {
|
||||
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: {
|
||||
ChatItemCount: (count: number) => `${count} messages en total`,
|
||||
|
@@ -5,7 +5,7 @@ const it: PartialLocaleType = {
|
||||
WIP: "Work in progress...",
|
||||
Error: {
|
||||
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: {
|
||||
ChatItemCount: (count: number) => `${count} messaggi`,
|
||||
|
@@ -5,7 +5,8 @@ import type { PartialLocaleType } from "./index";
|
||||
const ko: PartialLocaleType = {
|
||||
WIP: "곧 출시 예정...",
|
||||
Error: {
|
||||
Unauthorized: "권한이 없습니다. 설정 페이지에서 액세스 코드를 입력하세요.",
|
||||
Unauthorized:
|
||||
"권한이 없습니다. 설정 페이지에서 액세스 코드를 [입력하세요](/#/auth).",
|
||||
},
|
||||
ChatItem: {
|
||||
ChatItemCount: (count: number) => `${count}개의 메시지`,
|
||||
|
@@ -4,7 +4,8 @@ import type { PartialLocaleType } from "./index";
|
||||
const no: PartialLocaleType = {
|
||||
WIP: "Arbeid pågår ...",
|
||||
Error: {
|
||||
Unauthorized: "Du har ikke tilgang. Vennlig oppgi tildelt adgangskode.",
|
||||
Unauthorized:
|
||||
"Du har ikke tilgang. [Vennlig oppgi tildelt adgangskode](/#/auth).",
|
||||
},
|
||||
ChatItem: {
|
||||
ChatItemCount: (count: number) => `${count} meldinger`,
|
||||
|
@@ -5,7 +5,7 @@ const ru: PartialLocaleType = {
|
||||
WIP: "Скоро...",
|
||||
Error: {
|
||||
Unauthorized:
|
||||
"Несанкционированный доступ. Пожалуйста, введите код доступа на странице настроек.",
|
||||
"Несанкционированный доступ. Пожалуйста, введите код доступа на [странице](/#/auth) настроек.",
|
||||
},
|
||||
ChatItem: {
|
||||
ChatItemCount: (count: number) => `${count} сообщений`,
|
||||
|
@@ -5,7 +5,7 @@ const tr: PartialLocaleType = {
|
||||
WIP: "Çalışma devam ediyor...",
|
||||
Error: {
|
||||
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: {
|
||||
ChatItemCount: (count: number) => `${count} mesaj`,
|
||||
|
@@ -4,7 +4,7 @@ import type { PartialLocaleType } from "./index";
|
||||
const tw: PartialLocaleType = {
|
||||
WIP: "該功能仍在開發中……",
|
||||
Error: {
|
||||
Unauthorized: "目前您的狀態是未授權,請前往設定頁面輸入授權碼。",
|
||||
Unauthorized: "目前您的狀態是未授權,請前往[設定頁面](/#/auth)輸入授權碼。",
|
||||
},
|
||||
ChatItem: {
|
||||
ChatItemCount: (count: number) => `${count} 條對話`,
|
||||
|
@@ -332,7 +332,7 @@ export const useChatStore = create<ChatStore>()(
|
||||
},
|
||||
onError(error) {
|
||||
const isAborted = error.message.includes("aborted");
|
||||
botMessage.content =
|
||||
botMessage.content +=
|
||||
"\n\n" +
|
||||
prettyObject({
|
||||
error: true,
|
||||
@@ -553,7 +553,7 @@ export const useChatStore = create<ChatStore>()(
|
||||
date: "",
|
||||
}),
|
||||
),
|
||||
config: { ...modelConfig, stream: true },
|
||||
config: { ...modelConfig, stream: true, model: "gpt-3.5-turbo" },
|
||||
onUpdate(message) {
|
||||
session.memoryPrompt = message;
|
||||
},
|
||||
|
@@ -81,7 +81,7 @@ export const ModalConfigValidator = {
|
||||
return x as ModelType;
|
||||
},
|
||||
max_tokens(x: number) {
|
||||
return limitNumber(x, 0, 32000, 2000);
|
||||
return limitNumber(x, 0, 100000, 2000);
|
||||
},
|
||||
presence_penalty(x: number) {
|
||||
return limitNumber(x, -2, 2, 0);
|
||||
|
@@ -89,7 +89,7 @@
|
||||
html {
|
||||
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;
|
||||
}
|
||||
|
||||
|
@@ -39,7 +39,7 @@ Docker 版本相当于稳定版,latest Docker 总是与 latest release version
|
||||
|
||||
> 相关讨论:[#386](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/386)
|
||||
|
||||
如果你使用 ngnix 反向代理,需要在配置文件中增加下列代码:
|
||||
如果你使用 nginx 反向代理,需要在配置文件中增加下列代码:
|
||||
|
||||
```
|
||||
# 不缓存,支持流式输出
|
||||
@@ -212,7 +212,8 @@ OpenAI 网站计费说明:https://openai.com/pricing#language-models
|
||||
OpenAI 根据 token 数收费,1000 个 token 通常可代表 750 个英文单词,或 500 个汉字。输入(Prompt)和输出(Completion)分别统计费用。
|
||||
|模型|用户输入(Prompt)计费|模型输出(Completion)计费|每次交互最大 token 数|
|
||||
|----|----|----|----|
|
||||
|gpt-3.5|$0.002 / 1 千 tokens|$0.002 / 1 千 tokens|4096|
|
||||
|gpt-3.5-turbo|$0.0015 / 1 千 tokens|$0.002 / 1 千 tokens|4096|
|
||||
|gpt-3.5-turbo-16K|$0.003 / 1 千 tokens|$0.004 / 1 千 tokens|16384|
|
||||
|gpt-4|$0.03 / 1 千 tokens|$0.06 / 1 千 tokens|8192|
|
||||
|gpt-4-32K|$0.06 / 1 千 tokens|$0.12 / 1 千 tokens|32768|
|
||||
|
||||
|
@@ -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)
|
||||
|
||||
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; # 关闭缓存
|
||||
|
@@ -45,7 +45,7 @@
|
||||
"@tauri-apps/cli": "^1.4.0",
|
||||
"@types/node": "^20.3.3",
|
||||
"@types/react": "^18.2.14",
|
||||
"@types/react-dom": "^18.0.11",
|
||||
"@types/react-dom": "^18.2.7",
|
||||
"@types/react-katex": "^3.0.0",
|
||||
"@types/spark-md5": "^3.0.2",
|
||||
"cross-env": "^7.0.3",
|
||||
|
@@ -9,7 +9,7 @@
|
||||
},
|
||||
"package": {
|
||||
"productName": "ChatGPT Next Web",
|
||||
"version": "2.9.1"
|
||||
"version": "2.9.3"
|
||||
},
|
||||
"tauri": {
|
||||
"allowlist": {
|
||||
|
@@ -1505,10 +1505,10 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf"
|
||||
integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==
|
||||
|
||||
"@types/react-dom@^18.0.11":
|
||||
version "18.0.11"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.0.11.tgz#321351c1459bc9ca3d216aefc8a167beec334e33"
|
||||
integrity sha512-O38bPbI2CWtgw/OoQoY+BRelw7uysmXbWvw3nLWO21H1HSh+GOlqPuXshJfjmpNlKiiSDG9cc1JZAaMmVdcTlw==
|
||||
"@types/react-dom@^18.2.7":
|
||||
version "18.2.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.7.tgz#67222a08c0a6ae0a0da33c3532348277c70abb63"
|
||||
integrity sha512-GRaAEriuT4zp9N4p1i8BDBYmEyfo+xQ3yHjJU4eiK5NDa1RmUZG+unZABUTK4/Ox/M+GaHwb6Ow8rUITrtjszA==
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
|
||||
|
Reference in New Issue
Block a user