|
@@ -27,6 +27,7 @@ import Locale from "../locales";
|
|
|
|
|
|
import dynamic from "next/dynamic";
|
|
|
import { REPO_URL } from "../constant";
|
|
|
+import { ControllerPool } from "../requests";
|
|
|
|
|
|
export function Loading(props: { noLogo?: boolean }) {
|
|
|
return (
|
|
@@ -146,28 +147,67 @@ function useSubmitHandler() {
|
|
|
export function Chat(props: { showSideBar?: () => void }) {
|
|
|
type RenderMessage = Message & { preview?: boolean };
|
|
|
|
|
|
- const session = useChatStore((state) => state.currentSession());
|
|
|
+ const [session, sessionIndex] = useChatStore((state) => [
|
|
|
+ state.currentSession(),
|
|
|
+ state.currentSessionIndex,
|
|
|
+ ]);
|
|
|
const [userInput, setUserInput] = useState("");
|
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
|
const { submitKey, shouldSubmit } = useSubmitHandler();
|
|
|
|
|
|
const onUserInput = useChatStore((state) => state.onUserInput);
|
|
|
+
|
|
|
+ // submit user input
|
|
|
const onUserSubmit = () => {
|
|
|
if (userInput.length <= 0) return;
|
|
|
setIsLoading(true);
|
|
|
onUserInput(userInput).then(() => setIsLoading(false));
|
|
|
setUserInput("");
|
|
|
};
|
|
|
+
|
|
|
+ // stop response
|
|
|
+ const onUserStop = (messageIndex: number) => {
|
|
|
+ console.log(ControllerPool, sessionIndex, messageIndex);
|
|
|
+ ControllerPool.stop(sessionIndex, messageIndex);
|
|
|
+ };
|
|
|
+
|
|
|
+ // check if should send message
|
|
|
const onInputKeyDown = (e: KeyboardEvent) => {
|
|
|
if (shouldSubmit(e)) {
|
|
|
onUserSubmit();
|
|
|
e.preventDefault();
|
|
|
}
|
|
|
};
|
|
|
+ const onRightClick = (e: any, message: Message) => {
|
|
|
+ // auto fill user input
|
|
|
+ if (message.role === "user") {
|
|
|
+ setUserInput(message.content);
|
|
|
+ }
|
|
|
+
|
|
|
+ // copy to clipboard
|
|
|
+ if (selectOrCopy(e.currentTarget, message.content)) {
|
|
|
+ e.preventDefault();
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const onResend = (botIndex: number) => {
|
|
|
+ // find last user input message and resend
|
|
|
+ for (let i = botIndex; i >= 0; i -= 1) {
|
|
|
+ if (messages[i].role === "user") {
|
|
|
+ setIsLoading(true);
|
|
|
+ onUserInput(messages[i].content).then(() => setIsLoading(false));
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // for auto-scroll
|
|
|
const latestMessageRef = useRef<HTMLDivElement>(null);
|
|
|
|
|
|
- const [hoveringMessage, setHoveringMessage] = useState(false);
|
|
|
+ // wont scroll while hovering messages
|
|
|
+ const [autoScroll, setAutoScroll] = useState(false);
|
|
|
|
|
|
+ // preview messages
|
|
|
const messages = (session.messages as RenderMessage[])
|
|
|
.concat(
|
|
|
isLoading
|
|
@@ -194,10 +234,11 @@ export function Chat(props: { showSideBar?: () => void }) {
|
|
|
: []
|
|
|
);
|
|
|
|
|
|
+ // auto scroll
|
|
|
useLayoutEffect(() => {
|
|
|
setTimeout(() => {
|
|
|
const dom = latestMessageRef.current;
|
|
|
- if (dom && !isIOS() && !hoveringMessage) {
|
|
|
+ if (dom && !isIOS() && autoScroll) {
|
|
|
dom.scrollIntoView({
|
|
|
behavior: "smooth",
|
|
|
block: "end",
|
|
@@ -252,15 +293,7 @@ export function Chat(props: { showSideBar?: () => void }) {
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
- <div
|
|
|
- className={styles["chat-body"]}
|
|
|
- onMouseOver={() => {
|
|
|
- setHoveringMessage(true);
|
|
|
- }}
|
|
|
- onMouseOut={() => {
|
|
|
- setHoveringMessage(false);
|
|
|
- }}
|
|
|
- >
|
|
|
+ <div className={styles["chat-body"]}>
|
|
|
{messages.map((message, i) => {
|
|
|
const isUser = message.role === "user";
|
|
|
|
|
@@ -283,13 +316,20 @@ export function Chat(props: { showSideBar?: () => void }) {
|
|
|
<div className={styles["chat-message-item"]}>
|
|
|
{!isUser && (
|
|
|
<div className={styles["chat-message-top-actions"]}>
|
|
|
- {message.streaming && (
|
|
|
+ {message.streaming ? (
|
|
|
<div
|
|
|
className={styles["chat-message-top-action"]}
|
|
|
- onClick={() => showToast(Locale.WIP)}
|
|
|
+ onClick={() => onUserStop(i)}
|
|
|
>
|
|
|
{Locale.Chat.Actions.Stop}
|
|
|
</div>
|
|
|
+ ) : (
|
|
|
+ <div
|
|
|
+ className={styles["chat-message-top-action"]}
|
|
|
+ onClick={() => onResend(i)}
|
|
|
+ >
|
|
|
+ {Locale.Chat.Actions.Retry}
|
|
|
+ </div>
|
|
|
)}
|
|
|
|
|
|
<div
|
|
@@ -306,11 +346,7 @@ export function Chat(props: { showSideBar?: () => void }) {
|
|
|
) : (
|
|
|
<div
|
|
|
className="markdown-body"
|
|
|
- onContextMenu={(e) => {
|
|
|
- if (selectOrCopy(e.currentTarget, message.content)) {
|
|
|
- e.preventDefault();
|
|
|
- }
|
|
|
- }}
|
|
|
+ onContextMenu={(e) => onRightClick(e, message)}
|
|
|
>
|
|
|
<Markdown content={message.content} />
|
|
|
</div>
|
|
@@ -341,6 +377,9 @@ export function Chat(props: { showSideBar?: () => void }) {
|
|
|
onInput={(e) => setUserInput(e.currentTarget.value)}
|
|
|
value={userInput}
|
|
|
onKeyDown={(e) => onInputKeyDown(e as any)}
|
|
|
+ onFocus={() => setAutoScroll(true)}
|
|
|
+ onBlur={() => setAutoScroll(false)}
|
|
|
+ autoFocus
|
|
|
/>
|
|
|
<IconButton
|
|
|
icon={<SendWhiteIcon />}
|