mirror of
https://github.com/khoaliber/khoj.git
synced 2026-03-09 21:29:11 +00:00
Add a loading experience when waiting for khoj response
This commit is contained in:
@@ -13,7 +13,7 @@ import renderMathInElement from 'katex/contrib/auto-render';
|
|||||||
import 'katex/dist/katex.min.css';
|
import 'katex/dist/katex.min.css';
|
||||||
import 'highlight.js/styles/github.css'
|
import 'highlight.js/styles/github.css'
|
||||||
|
|
||||||
import Loading from '../loading/loading';
|
import Loading, { InlineLoading } from '../loading/loading';
|
||||||
|
|
||||||
import { Lightbulb } from "@phosphor-icons/react";
|
import { Lightbulb } from "@phosphor-icons/react";
|
||||||
|
|
||||||
@@ -34,12 +34,17 @@ interface ChatHistoryProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function constructTrainOfThought(trainOfThought: string[], lastMessage: boolean, key: string) {
|
function constructTrainOfThought(trainOfThought: string[], lastMessage: boolean, key: string, completed: boolean = false) {
|
||||||
const lastIndex = trainOfThought.length - 1;
|
const lastIndex = trainOfThought.length - 1;
|
||||||
return (
|
return (
|
||||||
<div className={`${styles.trainOfThought}`} key={key}>
|
<div className={`${styles.trainOfThought}`} key={key}>
|
||||||
|
{
|
||||||
|
!completed &&
|
||||||
|
<InlineLoading className='float-right' />
|
||||||
|
}
|
||||||
|
|
||||||
{trainOfThought.map((train, index) => (
|
{trainOfThought.map((train, index) => (
|
||||||
<TrainOfThought message={train} primary={index === lastIndex && lastMessage} />
|
<TrainOfThought message={train} primary={index === lastIndex && lastMessage && !completed} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@@ -59,70 +64,70 @@ export default function ChatHistory(props: ChatHistoryProps) {
|
|||||||
const [showReferencePanel, setShowReferencePanel] = useState(true);
|
const [showReferencePanel, setShowReferencePanel] = useState(true);
|
||||||
const [referencePanelData, setReferencePanelData] = useState<SingleChatMessage | null>(null);
|
const [referencePanelData, setReferencePanelData] = useState<SingleChatMessage | null>(null);
|
||||||
const [incompleteIncomingMessageIndex, setIncompleteIncomingMessageIndex] = useState<number | null>(null);
|
const [incompleteIncomingMessageIndex, setIncompleteIncomingMessageIndex] = useState<number | null>(null);
|
||||||
|
const [fetchingData, setFetchingData] = useState(false);
|
||||||
// useEffect(() => {
|
|
||||||
|
|
||||||
// // TODO add intersection observer to load more messages incrementally using parameter n=. Right now, it loads all messages at once.
|
|
||||||
|
|
||||||
// fetch(`/api/chat/history?client=web&conversation_id=${props.conversationId}`)
|
|
||||||
// .then(response => response.json())
|
|
||||||
// .then((chatData: ChatResponse) => {
|
|
||||||
// setLoading(false);
|
|
||||||
|
|
||||||
// // Render chat options, if any
|
|
||||||
// if (chatData) {
|
|
||||||
// setData(chatData.response);
|
|
||||||
// props.setTitle(chatData.response.slug);
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
// .catch(err => {
|
|
||||||
// console.error(err);
|
|
||||||
// return;
|
|
||||||
// });
|
|
||||||
// }, [props.conversationId]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log("hasMoreMessages", hasMoreMessages);
|
// This function ensures that scrolling to bottom happens after the data (chat messages) has been updated and rendered the first time.
|
||||||
|
const scrollToBottomAfterDataLoad = () => {
|
||||||
|
// Assume the data is loading in this scenario.
|
||||||
|
if (!data?.chat.length) {
|
||||||
|
setTimeout(() => {
|
||||||
|
scrollToBottom();
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (currentPage < 2) {
|
||||||
|
// Call the function defined above.
|
||||||
|
scrollToBottomAfterDataLoad();
|
||||||
|
}
|
||||||
|
|
||||||
|
}, [chatHistoryRef.current, data]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!hasMoreMessages || fetchingData) return;
|
||||||
|
|
||||||
|
// TODO: A future optimization would be to add a time to delay to re-enabling the intersection observer.
|
||||||
const observer = new IntersectionObserver(entries => {
|
const observer = new IntersectionObserver(entries => {
|
||||||
console.log("entries intersection observer", entries);
|
|
||||||
if (entries[0].isIntersecting && hasMoreMessages) {
|
if (entries[0].isIntersecting && hasMoreMessages) {
|
||||||
console.log("call fetchMoreMessages");
|
setFetchingData(true);
|
||||||
fetchMoreMessages(currentPage);
|
fetchMoreMessages(currentPage);
|
||||||
console.log("currentPage", currentPage);
|
|
||||||
setCurrentPage((prev) => prev + 1);
|
setCurrentPage((prev) => prev + 1);
|
||||||
}
|
}
|
||||||
}, { threshold: 1.0 });
|
}, { threshold: 1.0 });
|
||||||
|
|
||||||
if (sentinelRef.current) {
|
if (sentinelRef.current) {
|
||||||
console.log("observe sentinel");
|
|
||||||
observer.observe(sentinelRef.current);
|
observer.observe(sentinelRef.current);
|
||||||
}
|
}
|
||||||
|
|
||||||
return () => observer.disconnect();
|
return () => observer.disconnect();
|
||||||
}, [sentinelRef.current, hasMoreMessages, currentPage, props.conversationId]);
|
}, [sentinelRef.current, hasMoreMessages, currentPage, props.conversationId, fetchingData]);
|
||||||
|
|
||||||
const fetchMoreMessages = (currentPage: number) => {
|
const fetchMoreMessages = (currentPage: number) => {
|
||||||
if (!hasMoreMessages) return;
|
if (!hasMoreMessages || fetchingData) return;
|
||||||
|
|
||||||
console.log("fetchMoreMessages", currentPage);
|
console.log("fetchMoreMessages", currentPage);
|
||||||
|
|
||||||
const nextPage = currentPage + 1;
|
const nextPage = currentPage + 1;
|
||||||
fetch(`/api/chat/history?client=web&conversation_id=${props.conversationId}&n=${10*nextPage}`)
|
fetch(`/api/chat/history?client=web&conversation_id=${props.conversationId}&n=${10 * nextPage}`)
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then((chatData: ChatResponse) => {
|
.then((chatData: ChatResponse) => {
|
||||||
console.log(chatData);
|
console.log(chatData);
|
||||||
if (chatData && chatData.response && chatData.response.chat.length > 0) {
|
if (chatData && chatData.response && chatData.response.chat.length > 0) {
|
||||||
console.log(chatData);
|
|
||||||
|
|
||||||
if (chatData.response.chat.length === data?.chat.length) {
|
if (chatData.response.chat.length === data?.chat.length) {
|
||||||
setHasMoreMessages(false);
|
setHasMoreMessages(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
scrollToBottom();
|
|
||||||
|
|
||||||
setData(chatData.response);
|
setData(chatData.response);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
|
||||||
|
if (currentPage < 2) {
|
||||||
|
console.log("call scroll to bottom");
|
||||||
|
scrollToBottom();
|
||||||
|
}
|
||||||
|
setFetchingData(false);
|
||||||
} else {
|
} else {
|
||||||
console.log("No more messages");
|
console.log("No more messages");
|
||||||
setHasMoreMessages(false);
|
setHasMoreMessages(false);
|
||||||
@@ -251,7 +256,7 @@ export default function ChatHistory(props: ChatHistoryProps) {
|
|||||||
customClassName='fullHistory'
|
customClassName='fullHistory'
|
||||||
borderLeftColor='orange-500' />
|
borderLeftColor='orange-500' />
|
||||||
{
|
{
|
||||||
message.trainOfThought && constructTrainOfThought(message.trainOfThought, index === incompleteIncomingMessageIndex, `${index}trainOfThought`)
|
message.trainOfThought && constructTrainOfThought(message.trainOfThought, index === incompleteIncomingMessageIndex, `${index}trainOfThought`, message.completed)
|
||||||
}
|
}
|
||||||
<ChatMessage
|
<ChatMessage
|
||||||
key={`${index}incoming`}
|
key={`${index}incoming`}
|
||||||
|
|||||||
@@ -8,10 +8,14 @@ export default function Loading() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function InlineLoading() {
|
interface InlineLoadingProps {
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function InlineLoading(props: InlineLoadingProps) {
|
||||||
return (
|
return (
|
||||||
<button>
|
<button>
|
||||||
<CircleNotch className='animate-spin h-5 w-5 mr-3' />
|
<CircleNotch className={`animate-spin h-5 w-5 mr-3 ${props.className}`} />
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user