Only Auto Scroll when at Page Bottom and Add Button to Scroll to Page Bottom on Web App (#923)

Improve Scrolling on Chat page of Web app

- Details
  1. Only auto scroll Khoj's streamed response when scroll is near bottom of page
      Allows scrolling to other messages in conversation while Khoj is formulating and streaming its response
  2. Add button to scroll to bottom of the chat page
  3. Scroll to most recent conversation turn on conversation first load
      It's a better default to anchor to most recent conversation turn (i.e most recent user message)
  4. Smooth scroll when Khoj's chat response is streamed
      Previously the scroll would jitter during response streaming
  5. Anchor scroll position when fetch and render older messages in conversation
      Allow users to keep their scroll position when older messages are fetched from server and rendered

Resolves #758
This commit is contained in:
Shantanu Sakpal
2024-09-29 11:24:34 +05:30
committed by GitHub
parent 06777e1660
commit be8de1a1bd
3 changed files with 104 additions and 64 deletions

View File

@@ -4,7 +4,7 @@ import styles from "./chatMessage.module.css";
import markdownIt from "markdown-it";
import mditHljs from "markdown-it-highlightjs";
import React, { useEffect, useRef, useState } from "react";
import React, { useEffect, useRef, useState, forwardRef } from "react";
import { createRoot } from "react-dom/client";
import "katex/dist/katex.min.css";
@@ -275,7 +275,7 @@ export function TrainOfThought(props: TrainOfThoughtProps) {
);
}
export default function ChatMessage(props: ChatMessageProps) {
const ChatMessage = forwardRef<HTMLDivElement, ChatMessageProps>((props, ref) => {
const [copySuccess, setCopySuccess] = useState<boolean>(false);
const [isHovering, setIsHovering] = useState<boolean>(false);
const [textRendered, setTextRendered] = useState<string>("");
@@ -406,10 +406,6 @@ export default function ChatMessage(props: ChatMessageProps) {
}
}, [markdownRendered, isHovering, messageRef]);
if (!props.chatMessage.message) {
return null;
}
function formatDate(timestamp: string) {
// Format date in HH:MM, DD MMM YYYY format
let date = new Date(timestamp + "Z");
@@ -449,6 +445,9 @@ export default function ChatMessage(props: ChatMessageProps) {
function constructClasses(chatMessage: SingleChatMessage) {
let classes = [styles.chatMessageContainer, "shadow-md"];
classes.push(styles[chatMessage.by]);
if (!chatMessage.message) {
classes.push(styles.emptyChatMessage);
}
if (props.customClassName) {
classes.push(styles[`${chatMessage.by}${props.customClassName}`]);
@@ -478,17 +477,8 @@ export default function ChatMessage(props: ChatMessageProps) {
const sentenceRegex = /[^.!?]+[.!?]*/g;
const chunks = props.chatMessage.message.match(sentenceRegex) || [];
if (!chunks) {
return;
}
if (!chunks || chunks.length === 0 || !chunks[0]) return;
if (chunks.length === 0) {
return;
}
if (!chunks[0]) {
return;
}
setIsPlaying(true);
let nextBlobPromise = fetchBlob(chunks[0]);
@@ -548,6 +538,7 @@ export default function ChatMessage(props: ChatMessageProps) {
return (
<div
ref={ref}
className={constructClasses(props.chatMessage)}
onMouseLeave={(event) => setIsHovering(false)}
onMouseEnter={(event) => setIsHovering(true)}
@@ -640,4 +631,8 @@ export default function ChatMessage(props: ChatMessageProps) {
</div>
</div>
);
}
});
ChatMessage.displayName = "ChatMessage";
export default ChatMessage;