mirror of
https://github.com/sqlchat/sqlchat.git
synced 2025-08-02 14:01:59 +08:00
feat: add message request loader
This commit is contained in:
@ -2,7 +2,12 @@ import { useEffect } from "react";
|
||||
import { useChatStore } from "@/store";
|
||||
import Icon from "../Icon";
|
||||
|
||||
const Header = () => {
|
||||
interface Props {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const Header = (props: Props) => {
|
||||
const { className } = props;
|
||||
const chatStore = useChatStore();
|
||||
const currentChat = chatStore.currentChat;
|
||||
const title = currentChat?.title || "SQL Chat";
|
||||
@ -12,7 +17,11 @@ const Header = () => {
|
||||
}, [title]);
|
||||
|
||||
return (
|
||||
<div className="sticky top-0 w-full flex flex-row justify-between items-center lg:grid lg:grid-cols-3 py-2 border-b bg-white z-1">
|
||||
<div
|
||||
className={`${
|
||||
className || ""
|
||||
} sticky top-0 w-full flex flex-row justify-between items-center lg:grid lg:grid-cols-3 py-2 border-b bg-white z-1`}
|
||||
>
|
||||
<div className="ml-2 flex justify-center items-center">
|
||||
<label htmlFor="connection-drawer" className="w-8 h-8 p-1 mr-1 block lg:hidden rounded-md cursor-pointer hover:bg-gray-100">
|
||||
<Icon.IoIosMenu className="text-gray-600 w-full h-auto" />
|
||||
|
@ -56,6 +56,7 @@ const MessageTextarea = (props: Props) => {
|
||||
creatorRole: CreatorRole.User,
|
||||
createdAt: Date.now(),
|
||||
content: value,
|
||||
isGenerated: true,
|
||||
});
|
||||
setValue("");
|
||||
textareaRef.current!.value = "";
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { ReactElement } from "react";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import { useUserStore } from "@/store";
|
||||
@ -39,23 +40,20 @@ const MessageView = (props: Props) => {
|
||||
remarkPlugins={[remarkGfm]}
|
||||
components={{
|
||||
pre({ node, className, children, ...props }) {
|
||||
const child = children[0] as ReactElement;
|
||||
const match = /language-(\w+)/.exec(child.props.className || "");
|
||||
const language = match ? match[1] : "plain";
|
||||
return (
|
||||
<pre className={`${className || ""} p-0 w-full`} {...props}>
|
||||
{children}
|
||||
<CodeBlock
|
||||
key={Math.random()}
|
||||
language={language || "plain"}
|
||||
value={String(child.props.children).replace(/\n$/, "")}
|
||||
{...props}
|
||||
/>
|
||||
</pre>
|
||||
);
|
||||
},
|
||||
code({ node, inline, className, children, ...props }) {
|
||||
const match = /language-(\w+)/.exec(className || "");
|
||||
const language = match ? match[1] : "plain";
|
||||
return !inline ? (
|
||||
<CodeBlock key={Math.random()} language={language || "plain"} value={String(children).replace(/\n$/, "")} {...props} />
|
||||
) : (
|
||||
<code className={className} {...props}>
|
||||
{children}
|
||||
</code>
|
||||
);
|
||||
},
|
||||
}}
|
||||
>
|
||||
{message.content}
|
||||
|
@ -1,23 +1,37 @@
|
||||
import { head } from "lodash-es";
|
||||
import { head, last } from "lodash-es";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { toast } from "react-hot-toast";
|
||||
import { getAssistantById, getPromptGeneratorOfAssistant, useChatStore, useMessageStore, useConnectionStore } from "@/store";
|
||||
import { CreatorRole, Message } from "@/types";
|
||||
import { generateUUID } from "@/utils";
|
||||
import Icon from "../Icon";
|
||||
import Header from "./Header";
|
||||
import EmptyView from "../EmptyView";
|
||||
import MessageView from "./MessageView";
|
||||
import MessageTextarea from "./MessageTextarea";
|
||||
import MessageLoader from "../MessageLoader";
|
||||
|
||||
const ChatView = () => {
|
||||
const connectionStore = useConnectionStore();
|
||||
const chatStore = useChatStore();
|
||||
const messageStore = useMessageStore();
|
||||
const [isRequesting, setIsRequesting] = useState<boolean>(false);
|
||||
const [showHeaderShadow, setShowHeaderShadow] = useState<boolean>(false);
|
||||
const chatViewRef = useRef<HTMLDivElement>(null);
|
||||
const currentChat = chatStore.currentChat;
|
||||
const messageList = messageStore.messageList.filter((message) => message.chatId === currentChat?.id);
|
||||
const lastMessage = last(messageList);
|
||||
|
||||
// Toggle header shadow.
|
||||
useEffect(() => {
|
||||
const handleChatViewScroll = () => {
|
||||
setShowHeaderShadow((chatViewRef.current?.scrollTop || 0) > 0);
|
||||
};
|
||||
chatViewRef.current?.addEventListener("scroll", handleChatViewScroll);
|
||||
|
||||
return () => {
|
||||
chatViewRef.current?.removeEventListener("scroll", handleChatViewScroll);
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
@ -26,7 +40,22 @@ const ChatView = () => {
|
||||
}
|
||||
chatViewRef.current.scrollTop = chatViewRef.current.scrollHeight;
|
||||
});
|
||||
}, [currentChat, isRequesting]);
|
||||
}, [currentChat, messageList.length, lastMessage?.isGenerated]);
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
if (!chatViewRef.current) {
|
||||
return;
|
||||
}
|
||||
if (!lastMessage) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!lastMessage.isGenerated) {
|
||||
chatViewRef.current.scrollTop = chatViewRef.current.scrollHeight;
|
||||
}
|
||||
});
|
||||
}, [lastMessage?.isGenerated, lastMessage?.content]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!connectionStore.currentConnectionCtx) {
|
||||
@ -91,6 +120,7 @@ const ChatView = () => {
|
||||
creatorRole: CreatorRole.Assistant,
|
||||
createdAt: Date.now(),
|
||||
content: "",
|
||||
isGenerated: false,
|
||||
};
|
||||
messageStore.addMessage(message);
|
||||
|
||||
@ -103,11 +133,16 @@ const ChatView = () => {
|
||||
const char = decoder.decode(value);
|
||||
if (char) {
|
||||
message.content = message.content + char;
|
||||
messageStore.updateMessageContent(message.id, message.content);
|
||||
messageStore.updateMessage(message.id, {
|
||||
content: message.content,
|
||||
});
|
||||
}
|
||||
}
|
||||
done = readerDone;
|
||||
}
|
||||
messageStore.updateMessage(message.id, {
|
||||
isGenerated: true,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
@ -115,18 +150,14 @@ const ChatView = () => {
|
||||
ref={chatViewRef}
|
||||
className="drawer-content relative w-full h-full max-h-full flex flex-col justify-start items-start overflow-y-auto bg-white"
|
||||
>
|
||||
<Header />
|
||||
<Header className={showHeaderShadow ? "shadow" : ""} />
|
||||
<div className="p-2 w-full h-auto grow max-w-3xl py-1 px-4 sm:px-8 mx-auto">
|
||||
{messageList.length === 0 ? (
|
||||
<EmptyView className="mt-16" />
|
||||
<EmptyView className="mt-16" sendMessage={sendMessageToCurrentChat} />
|
||||
) : (
|
||||
messageList.map((message) => <MessageView key={message.id} message={message} />)
|
||||
)}
|
||||
{isRequesting && (
|
||||
<div className="w-full pt-4 pb-8 flex justify-center items-center text-gray-600">
|
||||
<Icon.BiLoader className="w-5 h-auto mr-2 animate-spin" /> Requesting...
|
||||
</div>
|
||||
)}
|
||||
{isRequesting && <MessageLoader />}
|
||||
</div>
|
||||
<div className="sticky bottom-0 w-full max-w-3xl py-2 px-4 sm:px-8 mx-auto bg-white bg-opacity-80 backdrop-blur">
|
||||
<MessageTextarea disabled={isRequesting} sendMessage={sendMessageToCurrentChat} />
|
||||
|
Reference in New Issue
Block a user