|
@@ -14,6 +14,11 @@ import DeleteIcon from "../icons/delete.svg";
|
|
|
import MaxIcon from "../icons/max.svg";
|
|
|
import MinIcon from "../icons/min.svg";
|
|
|
|
|
|
+import LightIcon from "../icons/light.svg";
|
|
|
+import DarkIcon from "../icons/dark.svg";
|
|
|
+import AutoIcon from "../icons/auto.svg";
|
|
|
+import BottomIcon from "../icons/bottom.svg";
|
|
|
+
|
|
|
import {
|
|
|
Message,
|
|
|
SubmitKey,
|
|
@@ -22,6 +27,7 @@ import {
|
|
|
ROLES,
|
|
|
createMessage,
|
|
|
useAccessStore,
|
|
|
+ Theme,
|
|
|
} from "../store";
|
|
|
|
|
|
import {
|
|
@@ -31,6 +37,7 @@ import {
|
|
|
isMobileScreen,
|
|
|
selectOrCopy,
|
|
|
autoGrowTextArea,
|
|
|
+ getCSSVar,
|
|
|
} from "../utils";
|
|
|
|
|
|
import dynamic from "next/dynamic";
|
|
@@ -60,7 +67,11 @@ export function Avatar(props: { role: Message["role"] }) {
|
|
|
const config = useChatStore((state) => state.config);
|
|
|
|
|
|
if (props.role !== "user") {
|
|
|
- return <BotIcon className={styles["user-avtar"]} />;
|
|
|
+ return (
|
|
|
+ <div className="no-dark">
|
|
|
+ <BotIcon className={styles["user-avtar"]} />
|
|
|
+ </div>
|
|
|
+ );
|
|
|
}
|
|
|
|
|
|
return (
|
|
@@ -316,22 +327,78 @@ function useScrollToBottom() {
|
|
|
// for auto-scroll
|
|
|
const scrollRef = useRef<HTMLDivElement>(null);
|
|
|
const [autoScroll, setAutoScroll] = useState(true);
|
|
|
-
|
|
|
- // auto scroll
|
|
|
- useLayoutEffect(() => {
|
|
|
+ const scrollToBottom = () => {
|
|
|
const dom = scrollRef.current;
|
|
|
- if (dom && autoScroll) {
|
|
|
+ if (dom) {
|
|
|
setTimeout(() => (dom.scrollTop = dom.scrollHeight), 1);
|
|
|
}
|
|
|
+ };
|
|
|
+
|
|
|
+ // auto scroll
|
|
|
+ useLayoutEffect(() => {
|
|
|
+ autoScroll && scrollToBottom();
|
|
|
});
|
|
|
|
|
|
return {
|
|
|
scrollRef,
|
|
|
autoScroll,
|
|
|
setAutoScroll,
|
|
|
+ scrollToBottom,
|
|
|
};
|
|
|
}
|
|
|
|
|
|
+export function ChatActions(props: {
|
|
|
+ showPromptModal: () => void;
|
|
|
+ scrollToBottom: () => void;
|
|
|
+ hitBottom: boolean;
|
|
|
+}) {
|
|
|
+ const chatStore = useChatStore();
|
|
|
+
|
|
|
+ const theme = chatStore.config.theme;
|
|
|
+
|
|
|
+ function nextTheme() {
|
|
|
+ const themes = [Theme.Auto, Theme.Light, Theme.Dark];
|
|
|
+ const themeIndex = themes.indexOf(theme);
|
|
|
+ const nextIndex = (themeIndex + 1) % themes.length;
|
|
|
+ const nextTheme = themes[nextIndex];
|
|
|
+ chatStore.updateConfig((config) => (config.theme = nextTheme));
|
|
|
+ }
|
|
|
+
|
|
|
+ return (
|
|
|
+ <div className={chatStyle["chat-input-actions"]}>
|
|
|
+ {!props.hitBottom && (
|
|
|
+ <div
|
|
|
+ className={`${chatStyle["chat-input-action"]} clickable`}
|
|
|
+ onClick={props.scrollToBottom}
|
|
|
+ >
|
|
|
+ <BottomIcon />
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+ {props.hitBottom && (
|
|
|
+ <div
|
|
|
+ className={`${chatStyle["chat-input-action"]} clickable`}
|
|
|
+ onClick={props.showPromptModal}
|
|
|
+ >
|
|
|
+ <BrainIcon />
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+
|
|
|
+ <div
|
|
|
+ className={`${chatStyle["chat-input-action"]} clickable`}
|
|
|
+ onClick={nextTheme}
|
|
|
+ >
|
|
|
+ {theme === Theme.Auto ? (
|
|
|
+ <AutoIcon />
|
|
|
+ ) : theme === Theme.Light ? (
|
|
|
+ <LightIcon />
|
|
|
+ ) : theme === Theme.Dark ? (
|
|
|
+ <DarkIcon />
|
|
|
+ ) : null}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+}
|
|
|
+
|
|
|
export function Chat(props: {
|
|
|
showSideBar?: () => void;
|
|
|
sideBarShowing?: boolean;
|
|
@@ -350,7 +417,7 @@ export function Chat(props: {
|
|
|
const [beforeInput, setBeforeInput] = useState("");
|
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
|
const { submitKey, shouldSubmit } = useSubmitHandler();
|
|
|
- const { scrollRef, setAutoScroll } = useScrollToBottom();
|
|
|
+ const { scrollRef, setAutoScroll, scrollToBottom } = useScrollToBottom();
|
|
|
const [hitBottom, setHitBottom] = useState(false);
|
|
|
|
|
|
const onChatBodyScroll = (e: HTMLElement) => {
|
|
@@ -683,6 +750,8 @@ export function Chat(props: {
|
|
|
if (!isMobileScreen()) return;
|
|
|
setUserInput(message.content);
|
|
|
}}
|
|
|
+ fontSize={fontSize}
|
|
|
+ parentRef={scrollRef}
|
|
|
/>
|
|
|
</div>
|
|
|
{!isUser && !message.preview && (
|
|
@@ -700,6 +769,12 @@ export function Chat(props: {
|
|
|
|
|
|
<div className={styles["chat-input-panel"]}>
|
|
|
<PromptHints prompts={promptHints} onPromptSelect={onPromptSelect} />
|
|
|
+
|
|
|
+ <ChatActions
|
|
|
+ showPromptModal={() => setShowPromptModal(true)}
|
|
|
+ scrollToBottom={scrollToBottom}
|
|
|
+ hitBottom={hitBottom}
|
|
|
+ />
|
|
|
<div className={styles["chat-input-panel-inner"]}>
|
|
|
<textarea
|
|
|
ref={inputRef}
|