|
|
|
@ -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 />}
|
|
|
|
|