KBaba7/llama.cpp
0
1import { useEffect, useRef, useState } from 'react';2import { useAppContext } from '../utils/app.context';3import StorageUtils from '../utils/storage';4import { useNavigate } from 'react-router';5import ChatMessage from './ChatMessage';6import { PendingMessage } from '../utils/types';7 8export default function ChatScreen() {9 const {10 viewingConversation,11 sendMessage,12 isGenerating,13 stopGenerating,14 pendingMessages,15 } = useAppContext();16 const [inputMsg, setInputMsg] = useState('');17 const containerRef = useRef<HTMLDivElement>(null);18 const navigate = useNavigate();19 20 const currConvId = viewingConversation?.id ?? '';21 const pendingMsg: PendingMessage | undefined = pendingMessages[currConvId];22 23 const scrollToBottom = (requiresNearBottom: boolean) => {24 if (!containerRef.current) return;25 const msgListElem = containerRef.current;26 const spaceToBottom =27 msgListElem.scrollHeight -28 msgListElem.scrollTop -29 msgListElem.clientHeight;30 if (!requiresNearBottom || spaceToBottom < 50) {31 setTimeout(32 () => msgListElem.scrollTo({ top: msgListElem.scrollHeight }),33 134 );35 }36 };37 38 // scroll to bottom when conversation changes39 useEffect(() => {40 scrollToBottom(false);41 }, [viewingConversation?.id]);42 43 const sendNewMessage = async () => {44 if (inputMsg.trim().length === 0 || isGenerating(currConvId)) return;45 const convId = viewingConversation?.id ?? StorageUtils.getNewConvId();46 const lastInpMsg = inputMsg;47 setInputMsg('');48 if (!viewingConversation) {49 // if user is creating a new conversation, redirect to the new conversation50 navigate(`/chat/${convId}`);51 }52 scrollToBottom(false);53 // auto scroll as message is being generated54 const onChunk = () => scrollToBottom(true);55 if (!(await sendMessage(convId, inputMsg, onChunk))) {56 // restore the input message if failed57 setInputMsg(lastInpMsg);58 }59 };60 61 return (62 <>63 {/* chat messages */}64 <div65 id="messages-list"66 className="flex flex-col grow overflow-y-auto"67 ref={containerRef}68 >69 <div className="mt-auto flex justify-center">70 {/* placeholder to shift the message to the bottom */}71 {viewingConversation ? '' : 'Send a message to start'}72 </div>73 {viewingConversation?.messages.map((msg) => (74 <ChatMessage key={msg.id} msg={msg} scrollToBottom={scrollToBottom} />75 ))}76 77 {pendingMsg && (78 <ChatMessage79 msg={pendingMsg}80 scrollToBottom={scrollToBottom}81 isPending82 id="pending-msg"83 />84 )}85 </div>86 87 {/* chat input */}88 <div className="flex flex-row items-center mt-8 mb-6">89 <textarea90 className="textarea textarea-bordered w-full"91 placeholder="Type a message (Shift+Enter to add a new line)"92 value={inputMsg}93 onChange={(e) => setInputMsg(e.target.value)}94 onKeyDown={(e) => {95 if (e.key === 'Enter' && e.shiftKey) return;96 if (e.key === 'Enter' && !e.shiftKey) {97 e.preventDefault();98 sendNewMessage();99 }100 }}101 id="msg-input"102 dir="auto"103 ></textarea>104 {isGenerating(currConvId) ? (105 <button106 className="btn btn-neutral ml-2"107 onClick={() => stopGenerating(currConvId)}108 >109 Stop110 </button>111 ) : (112 <button113 className="btn btn-primary ml-2"114 onClick={sendNewMessage}115 disabled={inputMsg.trim().length === 0}116 >117 Send118 </button>119 )}120 </div>121 </>122 );123}124 