'use client' import { useState, useEffect, useRef } from 'react' import { useChat } from '@ai-sdk/react' import { DefaultChatTransport, type UIMessage } from 'ai' import { Button } from '~/components/ui/button' import { Textarea } from '~/components/ui/textarea' import { SignInButton } from '@clerk/nextjs' import { useGsapContext, } from '~/app/_providers/GsapProvicer'; import Messages from './Messages' import { DeleteIcon } from 'lucide-react'; import { Spinner } from '~/components/ui/spinner'; import { useMessages } from '~/app/_providers/MessagesProvider'; interface DBMessage { id: string role: 'user' | 'assistant' content: string } interface ChatInterfaceProps { sessionId?: string, dbMessages: DBMessage[], } function SignInChatPrompt() { return (

Sign in to use the chat

You need to be signed in before you can talk to Gregor's AI assistant.

) } function toUIMessages(dbMessages: DBMessage[]): UIMessage[] { return dbMessages.map((m) => ({ id: m.id, role: m.role, parts: [{ type: 'text' as const, text: m.content }], })) } function addInitMessage(messageArray: UIMessage[]) { if (messageArray.at(0)?.id != 'init') { messageArray.unshift({ id: "init", role: 'assistant', parts: [{ type: 'text', text: "Hi, I'm Gregor's AI assistant. Ask me about his experience, projects, blog posts, or availability for a meeting." }], }) } } function AuthenticatedChatInterface({ dbMessages, sessionId }: ChatInterfaceProps & { sessionId: string }) { const [input, setInput] = useState('') const { clearingChat, clearChat, refetchMessages } = useMessages(); const initialMessages = toUIMessages(dbMessages) addInitMessage(initialMessages) const { messages, sendMessage, status, error, clearError, setMessages } = useChat({ transport: new DefaultChatTransport({ api: '/api/chat', body: { sessionId }, }), messages: initialMessages, }) useEffect(() => { return () => { refetchMessages() } }, []) const handleSend = () => { const text = input.trim() if (!text || status != 'ready' || clearingChat) return setInput('') sendMessage({ text }) } const gsapContext = useGsapContext() const didInitialScroll = useRef(false) useEffect(() => { const scroller = gsapContext?.getScroller() if (!scroller || scroller instanceof Window) { return } // Jump instantly on first open so the chat starts pinned to the bottom; // animate subsequent updates. Defer a frame so the messages have laid out // (and any streaming content has grown) before we measure scrollHeight. const behavior: ScrollBehavior = didInitialScroll.current ? 'smooth' : 'auto' didInitialScroll.current = true requestAnimationFrame(() => { scroller.scrollTo({ behavior, top: scroller.scrollHeight }) }) }, [messages, status]) return (
{messages && } {error && (
{error.message.includes('quota') || error.message.includes('429') ? 'OpenAI quota exceeded. Please try again later.' : `Error: ${error.message}`}
)}