mirror of
https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web.git
synced 2025-10-11 20:43:42 +08:00
Merge branch 'main' of https://github.com/Yidadaa/ChatGPT-Next-Web
This commit is contained in:
@@ -3,8 +3,8 @@ import React, {
|
||||
useState,
|
||||
useRef,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useMemo,
|
||||
useCallback,
|
||||
} from "react";
|
||||
|
||||
import SendWhiteIcon from "../icons/send-white.svg";
|
||||
@@ -27,6 +27,7 @@ import DarkIcon from "../icons/dark.svg";
|
||||
import AutoIcon from "../icons/auto.svg";
|
||||
import BottomIcon from "../icons/bottom.svg";
|
||||
import StopIcon from "../icons/pause.svg";
|
||||
import RobotIcon from "../icons/robot.svg";
|
||||
|
||||
import {
|
||||
ChatMessage,
|
||||
@@ -38,6 +39,7 @@ import {
|
||||
Theme,
|
||||
useAppConfig,
|
||||
DEFAULT_TOPIC,
|
||||
ALL_MODELS,
|
||||
} from "../store";
|
||||
|
||||
import {
|
||||
@@ -64,7 +66,7 @@ import { LAST_INPUT_KEY, Path, REQUEST_TIMEOUT_MS } from "../constant";
|
||||
import { Avatar } from "./emoji";
|
||||
import { MaskAvatar, MaskConfig } from "./mask";
|
||||
import { useMaskStore } from "../store/mask";
|
||||
import { useCommand } from "../command";
|
||||
import { ChatCommandPrefix, useChatCommand, useCommand } from "../command";
|
||||
import { prettyObject } from "../utils/format";
|
||||
import { ExportMessageModal } from "./exporter";
|
||||
import { getClientConfig } from "../config/client";
|
||||
@@ -206,8 +208,7 @@ export function PromptHints(props: {
|
||||
|
||||
useEffect(() => {
|
||||
const onKeyDown = (e: KeyboardEvent) => {
|
||||
if (noPrompts) return;
|
||||
if (e.metaKey || e.altKey || e.ctrlKey) {
|
||||
if (noPrompts || e.metaKey || e.altKey || e.ctrlKey) {
|
||||
return;
|
||||
}
|
||||
// arrow up / down to select prompt
|
||||
@@ -341,15 +342,15 @@ function useScrollToBottom() {
|
||||
// for auto-scroll
|
||||
const scrollRef = useRef<HTMLDivElement>(null);
|
||||
const [autoScroll, setAutoScroll] = useState(true);
|
||||
const scrollToBottom = () => {
|
||||
const scrollToBottom = useCallback(() => {
|
||||
const dom = scrollRef.current;
|
||||
if (dom) {
|
||||
setTimeout(() => (dom.scrollTop = dom.scrollHeight), 1);
|
||||
requestAnimationFrame(() => dom.scrollTo(0, dom.scrollHeight));
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
// auto scroll
|
||||
useLayoutEffect(() => {
|
||||
useEffect(() => {
|
||||
autoScroll && scrollToBottom();
|
||||
});
|
||||
|
||||
@@ -385,6 +386,19 @@ export function ChatActions(props: {
|
||||
const couldStop = ChatControllerPool.hasPending();
|
||||
const stopAll = () => ChatControllerPool.stopAll();
|
||||
|
||||
// switch model
|
||||
const currentModel = chatStore.currentSession().mask.modelConfig.model;
|
||||
function nextModel() {
|
||||
const models = ALL_MODELS.filter((m) => m.available).map((m) => m.name);
|
||||
const modelIndex = models.indexOf(currentModel);
|
||||
const nextIndex = (modelIndex + 1) % models.length;
|
||||
const nextModel = models[nextIndex];
|
||||
chatStore.updateCurrentSession((session) => {
|
||||
session.mask.modelConfig.model = nextModel;
|
||||
session.mask.syncGlobalConfig = false;
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={chatStyle["chat-input-actions"]}>
|
||||
{couldStop && (
|
||||
@@ -453,6 +467,12 @@ export function ChatActions(props: {
|
||||
});
|
||||
}}
|
||||
/>
|
||||
|
||||
<ChatAction
|
||||
onClick={nextModel}
|
||||
text={currentModel}
|
||||
icon={<RobotIcon />}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -489,16 +509,19 @@ export function Chat() {
|
||||
const [promptHints, setPromptHints] = useState<Prompt[]>([]);
|
||||
const onSearch = useDebouncedCallback(
|
||||
(text: string) => {
|
||||
setPromptHints(promptStore.search(text));
|
||||
const matchedPrompts = promptStore.search(text);
|
||||
setPromptHints(matchedPrompts);
|
||||
},
|
||||
100,
|
||||
{ leading: true, trailing: true },
|
||||
);
|
||||
|
||||
const onPromptSelect = (prompt: Prompt) => {
|
||||
setPromptHints([]);
|
||||
inputRef.current?.focus();
|
||||
setTimeout(() => setUserInput(prompt.content), 60);
|
||||
setTimeout(() => {
|
||||
setPromptHints([]);
|
||||
setUserInput(prompt.content);
|
||||
inputRef.current?.focus();
|
||||
}, 30);
|
||||
};
|
||||
|
||||
// auto grow input
|
||||
@@ -522,6 +545,19 @@ export function Chat() {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
useEffect(measure, [userInput]);
|
||||
|
||||
// chat commands shortcuts
|
||||
const chatCommands = useChatCommand({
|
||||
new: () => chatStore.newSession(),
|
||||
newm: () => navigate(Path.NewChat),
|
||||
prev: () => chatStore.nextSession(-1),
|
||||
next: () => chatStore.nextSession(1),
|
||||
clear: () =>
|
||||
chatStore.updateCurrentSession(
|
||||
(session) => (session.clearContextIndex = session.messages.length),
|
||||
),
|
||||
del: () => chatStore.deleteSession(chatStore.currentSessionIndex),
|
||||
});
|
||||
|
||||
// only search prompts when user input is short
|
||||
const SEARCH_TEXT_LIMIT = 30;
|
||||
const onInput = (text: string) => {
|
||||
@@ -531,6 +567,8 @@ export function Chat() {
|
||||
// clear search results
|
||||
if (n === 0) {
|
||||
setPromptHints([]);
|
||||
} else if (text.startsWith(ChatCommandPrefix)) {
|
||||
setPromptHints(chatCommands.search(text));
|
||||
} else if (!config.disablePromptHint && n < SEARCH_TEXT_LIMIT) {
|
||||
// check if need to trigger auto completion
|
||||
if (text.startsWith("/")) {
|
||||
@@ -542,6 +580,13 @@ export function Chat() {
|
||||
|
||||
const doSubmit = (userInput: string) => {
|
||||
if (userInput.trim() === "") return;
|
||||
const matchCommand = chatCommands.match(userInput);
|
||||
if (matchCommand.matched) {
|
||||
setUserInput("");
|
||||
setPromptHints([]);
|
||||
matchCommand.invoke();
|
||||
return;
|
||||
}
|
||||
setIsLoading(true);
|
||||
chatStore.onUserInput(userInput).then(() => setIsLoading(false));
|
||||
localStorage.setItem(LAST_INPUT_KEY, userInput);
|
||||
@@ -605,6 +650,10 @@ export function Chat() {
|
||||
const onRightClick = (e: any, message: ChatMessage) => {
|
||||
// copy to clipboard
|
||||
if (selectOrCopy(e.currentTarget, message.content)) {
|
||||
if (userInput.length === 0) {
|
||||
setUserInput(message.content);
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
|
@@ -185,7 +185,7 @@
|
||||
|
||||
.chat-item-delete {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
top: 0;
|
||||
right: 0;
|
||||
transition: all ease 0.3s;
|
||||
opacity: 0;
|
||||
@@ -194,7 +194,7 @@
|
||||
|
||||
.chat-item:hover > .chat-item-delete {
|
||||
opacity: 0.5;
|
||||
transform: translateX(-10px);
|
||||
transform: translateX(-4px);
|
||||
}
|
||||
|
||||
.chat-item:hover > .chat-item-delete:hover {
|
||||
@@ -283,15 +283,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.chat-item-delete {
|
||||
top: 15px;
|
||||
}
|
||||
|
||||
.chat-item:hover > .chat-item-delete {
|
||||
opacity: 0.5;
|
||||
right: 5px;
|
||||
}
|
||||
|
||||
.sidebar-tail {
|
||||
flex-direction: column-reverse;
|
||||
align-items: center;
|
||||
@@ -567,3 +558,7 @@
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.rtl-screen {
|
||||
direction: rtl;
|
||||
}
|
||||
|
@@ -15,6 +15,8 @@ import dynamic from "next/dynamic";
|
||||
import { Path, SlotID } from "../constant";
|
||||
import { ErrorBoundary } from "./error";
|
||||
|
||||
import { getLang } from "../locales";
|
||||
|
||||
import {
|
||||
HashRouter as Router,
|
||||
Routes,
|
||||
@@ -124,7 +126,7 @@ function Screen() {
|
||||
config.tightBorder && !isMobileScreen
|
||||
? styles["tight-container"]
|
||||
: styles.container
|
||||
}`
|
||||
} ${getLang() === "ar" ? styles["rtl-screen"] : ""}`
|
||||
}
|
||||
>
|
||||
{isAuth ? (
|
||||
|
@@ -195,6 +195,7 @@ export function Markdown(
|
||||
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}
|
||||
|
@@ -38,13 +38,10 @@ function useHotKey() {
|
||||
useEffect(() => {
|
||||
const onKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.metaKey || e.altKey || e.ctrlKey) {
|
||||
const n = chatStore.sessions.length;
|
||||
const limit = (x: number) => (x + n) % n;
|
||||
const i = chatStore.currentSessionIndex;
|
||||
if (e.key === "ArrowUp") {
|
||||
chatStore.selectSession(limit(i - 1));
|
||||
chatStore.nextSession(-1);
|
||||
} else if (e.key === "ArrowDown") {
|
||||
chatStore.selectSession(limit(i + 1));
|
||||
chatStore.nextSession(1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
Reference in New Issue
Block a user