mirror of
https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web.git
synced 2025-10-03 00:26:40 +08:00
Merge branch 'main' of https://github.com/Yidadaa/ChatGPT-Next-Web
This commit is contained in:
commit
ecba3450b4
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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,50 +874,98 @@ export function Chat() {
|
|||||||
context.push(copiedHello);
|
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
|
// clear context index = context length + index in messages
|
||||||
const clearContextIndex =
|
const clearContextIndex =
|
||||||
(session.clearContextIndex ?? -1) >= 0
|
(session.clearContextIndex ?? -1) >= 0
|
||||||
? session.clearContextIndex! + context.length
|
? session.clearContextIndex! + context.length - msgRenderIndex
|
||||||
: -1;
|
: -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 [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>;
|
||||||
|
}
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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(
|
||||||
|
@ -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>
|
||||||
|
@ -109,3 +109,6 @@ export const DEFAULT_MODELS = [
|
|||||||
available: true,
|
available: true,
|
||||||
},
|
},
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
|
export const CHAT_PAGE_SIZE = 10;
|
||||||
|
export const MAX_RENDER_MSG_COUNT = 20;
|
||||||
|
@ -19,6 +19,7 @@ const cn = {
|
|||||||
Chat: {
|
Chat: {
|
||||||
SubTitle: (count: number) => `共 ${count} 条对话`,
|
SubTitle: (count: number) => `共 ${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: "恢复上下文",
|
||||||
},
|
},
|
||||||
|
@ -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`,
|
||||||
|
@ -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`,
|
||||||
|
@ -21,6 +21,7 @@ const en: LocaleType = {
|
|||||||
Chat: {
|
Chat: {
|
||||||
SubTitle: (count: number) => `${count} messages`,
|
SubTitle: (count: number) => `${count} messages`,
|
||||||
EditMessage: {
|
EditMessage: {
|
||||||
|
Title: "Edit All Messages",
|
||||||
Topic: {
|
Topic: {
|
||||||
Title: "Topic",
|
Title: "Topic",
|
||||||
SubTitle: "Change the current topic",
|
SubTitle: "Change the current topic",
|
||||||
|
@ -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`,
|
||||||
|
@ -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`,
|
||||||
|
@ -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`,
|
||||||
|
@ -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}개의 메시지`,
|
||||||
|
@ -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`,
|
||||||
|
@ -5,7 +5,7 @@ const ru: PartialLocaleType = {
|
|||||||
WIP: "Скоро...",
|
WIP: "Скоро...",
|
||||||
Error: {
|
Error: {
|
||||||
Unauthorized:
|
Unauthorized:
|
||||||
"Несанкционированный доступ. Пожалуйста, введите код доступа на странице настроек.",
|
"Несанкционированный доступ. Пожалуйста, введите код доступа на [странице](/#/auth) настроек.",
|
||||||
},
|
},
|
||||||
ChatItem: {
|
ChatItem: {
|
||||||
ChatItemCount: (count: number) => `${count} сообщений`,
|
ChatItemCount: (count: number) => `${count} сообщений`,
|
||||||
|
@ -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`,
|
||||||
|
@ -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} 條對話`,
|
||||||
|
@ -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);
|
||||||
|
@ -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": {
|
||||||
|
Loading…
Reference in New Issue
Block a user