import { useRef, useState, useEffect, useMemo } from 'react'; import Layout from '@/components/layout'; import styles from '@/styles/Home.module.css'; import { Message } from '@/types/chat'; import { fetchEventSource } from '@microsoft/fetch-event-source'; import Image from 'next/image'; import ReactMarkdown from 'react-markdown'; import LoadingDots from '@/components/ui/LoadingDots'; export default function Home() { const [query, setQuery] = useState(''); const [loading, setLoading] = useState(false); const [messageState, setMessageState] = useState<{ messages: Message[]; pending?: string; history: [string, string][]; }>({ messages: [ { message: 'Hi there its Tom! What would like to learn about notion?', type: 'apiMessage', }, ], history: [], }); const { messages, pending, history } = messageState; const messageListRef = useRef(null); const textAreaRef = useRef(null); useEffect(() => { textAreaRef.current?.focus(); }, []); //handle form submission async function handleSubmit(e: any) { e.preventDefault(); if (!query) { alert('Please input a question'); return; } const question = query.trim(); setMessageState((state) => ({ ...state, messages: [ ...state.messages, { type: 'userMessage', message: question, }, ], pending: undefined, })); setLoading(true); setQuery(''); setMessageState((state) => ({ ...state, pending: '' })); const ctrl = new AbortController(); try { fetchEventSource('/api/chat', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ question, history, }), signal: ctrl.signal, onmessage: (event) => { if (event.data === '[DONE]') { setMessageState((state) => ({ history: [...state.history, [question, state.pending ?? '']], messages: [ ...state.messages, { type: 'apiMessage', message: state.pending ?? '', }, ], pending: undefined, })); setLoading(false); ctrl.abort(); } else { const data = JSON.parse(event.data); setMessageState((state) => ({ ...state, pending: (state.pending ?? '') + data.data, })); } }, }); } catch (error) { setLoading(false); console.log('error', error); } } //prevent empty submissions const handleEnter = (e: any) => { if (e.key === 'Enter' && query) { handleSubmit(e); } else if (e.key == 'Enter') { e.preventDefault(); } }; const chatMessages = useMemo(() => { return [ ...messages, ...(pending ? [{ type: 'apiMessage', message: pending }] : []), ]; }, [messages, pending]); return ( <>

Thomas Frank Notion Guide ChatBot

{chatMessages.map((message, index) => { let icon; let className; if (message.type === 'apiMessage') { icon = ( AI ); className = styles.apimessage; } else { icon = ( Me ); // The latest message sent by the user will be animated while waiting for a response className = loading && index === chatMessages.length - 1 ? styles.usermessagewaiting : styles.usermessage; } return (
{icon}
{message.message}
); })}