fix chat
This commit is contained in:
@@ -1,23 +1,25 @@
|
||||
'use client'
|
||||
import { useRef, useState, useEffect } from 'react'
|
||||
import { useState, useEffect } 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 { cn } from '~/lib/utils'
|
||||
import Markdown from 'react-markdown';
|
||||
import { AssistantMessage } from './AssistantMessage';
|
||||
import { UserMessage } from './UserMessage';
|
||||
|
||||
type DBMessage = {
|
||||
import {
|
||||
useGsapContext,
|
||||
} from '~/app/_providers/GsapProvicer';
|
||||
import Messages from './Messages'
|
||||
import { DeleteIcon } from 'lucide-react';
|
||||
import { trpc } from '~/app/_trpc/Client'
|
||||
import { Spinner } from '~/components/ui/spinner';
|
||||
interface DBMessage {
|
||||
id: string
|
||||
role: 'user' | 'assistant'
|
||||
content: string
|
||||
}
|
||||
|
||||
interface ChatInterfaceProps {
|
||||
sessionId: string
|
||||
initialMessages: DBMessage[]
|
||||
sessionId?: string
|
||||
// initialMessages: DBMessage[]
|
||||
}
|
||||
|
||||
function toUIMessages(dbMessages: DBMessage[]): UIMessage[] {
|
||||
@@ -27,60 +29,80 @@ function toUIMessages(dbMessages: DBMessage[]): UIMessage[] {
|
||||
parts: [{ type: 'text' as const, text: m.content }],
|
||||
}))
|
||||
}
|
||||
|
||||
export default function ChatInterface({ sessionId, initialMessages }: ChatInterfaceProps) {
|
||||
const [input, setInput] = useState('')
|
||||
const messagesEndRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
const { messages, sendMessage, status, error, clearError } = useChat({
|
||||
transport: new DefaultChatTransport({
|
||||
api: '/api/chat',
|
||||
body: { sessionId },
|
||||
}),
|
||||
messages: toUIMessages(initialMessages),
|
||||
})
|
||||
|
||||
const isLoading = status === 'submitted' || status === 'streaming'
|
||||
const hasError = status === 'error'
|
||||
|
||||
export default function ChatInterface({ sessionId }: ChatInterfaceProps) {
|
||||
const utils = trpc.useUtils();
|
||||
const { data: dbMessages, refetch: refetchMessages } = trpc.chat.getMessages.useQuery(sessionId ? sessionId : "")
|
||||
const [messages, setMessages] = useState<UIMessage[]>([]);
|
||||
function addMessage(newMessage: UIMessage) {
|
||||
setMessages(prev => [...prev, newMessage]);
|
||||
}
|
||||
useEffect(() => {
|
||||
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' })
|
||||
}, [messages])
|
||||
setMessages(toUIMessages(dbMessages ?? []));
|
||||
}, [dbMessages]);
|
||||
|
||||
if (messages.at(0)?.id != 'init') {
|
||||
messages.unshift({
|
||||
id: "init",
|
||||
role: 'assistant',
|
||||
parts: [{
|
||||
type: 'text',
|
||||
text: "Hi im gregors ai assistant,you can ask me to provide general information or to schedule a meeting."
|
||||
}],
|
||||
})
|
||||
}
|
||||
const [input, setInput] = useState('')
|
||||
const { sendMessage, status, error, clearError } = useChat({
|
||||
transport: new DefaultChatTransport({
|
||||
api: '/api/chat', body: { sessionId },
|
||||
}),
|
||||
messages: messages,
|
||||
})
|
||||
const handleSend = () => {
|
||||
const text = input.trim()
|
||||
if (!text || isLoading) return
|
||||
if (!text || status != 'ready') return
|
||||
setInput('')
|
||||
sendMessage({ text })
|
||||
addMessage({
|
||||
id: "", role: "user", parts: [
|
||||
{ type: 'text', text }
|
||||
]
|
||||
})
|
||||
addMessage({
|
||||
id: "", role: "assistant", parts: [
|
||||
{ type: 'text', text: "Thinking..." }
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
const clearChatMutation = trpc.chat.clearChat.useMutation()
|
||||
const handleClear = () => {
|
||||
clearChatMutation.mutate(undefined, {
|
||||
onSuccess: () => {
|
||||
utils.chat.getMessages.invalidate()
|
||||
refetchMessages()
|
||||
}
|
||||
})
|
||||
}
|
||||
const gsapContext = useGsapContext()
|
||||
useEffect(() => {
|
||||
console.log(status)
|
||||
if (status == 'ready') {
|
||||
utils.chat.getMessages.invalidate();
|
||||
refetchMessages()
|
||||
}
|
||||
}, [status])
|
||||
useEffect(() => {
|
||||
let scroller = gsapContext?.getScroller()
|
||||
if (scroller instanceof Window) {
|
||||
return;
|
||||
}
|
||||
console.log(scroller?.scrollHeight)
|
||||
scroller?.scrollTo({ behavior: 'smooth', top: scroller.scrollHeight })
|
||||
}, [messages])
|
||||
return (
|
||||
<div className="flex flex-col h-full">
|
||||
<div className="flex-1 overflow-y-auto p-4 space-y-4">
|
||||
{messages.length === 0 && (
|
||||
<div className="text-center text-muted-foreground py-12">
|
||||
<p className="text-base font-medium mb-1">Hi! I'm Gregor's AI recruiter assistant.</p>
|
||||
<p className="text-sm">Ask me about his skills and experience, or schedule a meeting!</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{messages.map((message) => (
|
||||
<>
|
||||
{message.role == 'assistant' && <AssistantMessage message={message}/>}
|
||||
{message.role == 'user' && <UserMessage message={message}/>}
|
||||
</>
|
||||
))}
|
||||
|
||||
{isLoading && (
|
||||
<div className="flex justify-start">
|
||||
<div className="bg-muted rounded-lg px-4 py-2 text-sm">
|
||||
<span className="animate-pulse">Thinking…</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div ref={messagesEndRef} />
|
||||
</div>
|
||||
{messages &&
|
||||
<Messages messages={messages} />
|
||||
}
|
||||
|
||||
{error && (
|
||||
<div className="mx-4 mb-2 flex items-start gap-2 rounded-lg border border-destructive/50 bg-destructive/10 px-3 py-2 text-sm text-destructive">
|
||||
@@ -89,19 +111,20 @@ export default function ChatInterface({ sessionId, initialMessages }: ChatInterf
|
||||
? 'OpenAI quota exceeded. Please try again later.'
|
||||
: `Error: ${error.message}`}
|
||||
</span>
|
||||
<button
|
||||
<Button
|
||||
type="button"
|
||||
onClick={clearError}
|
||||
className="shrink-0 opacity-60 hover:opacity-100"
|
||||
aria-label="Dismiss"
|
||||
variant='destructive'
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
<DeleteIcon />
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="p-4 border-t flex gap-2">
|
||||
<div className="p-4 border-t flex flex-row gap-2">
|
||||
<Textarea
|
||||
name='message'
|
||||
value={input}
|
||||
onChange={(e) => setInput(e.target.value)}
|
||||
placeholder="Ask about Gregor's experience or schedule a meeting…"
|
||||
@@ -114,13 +137,24 @@ export default function ChatInterface({ sessionId, initialMessages }: ChatInterf
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
onClick={handleSend}
|
||||
disabled={isLoading || hasError || !input.trim()}
|
||||
className="self-end"
|
||||
>
|
||||
Send
|
||||
</Button>
|
||||
<div className='flex flex-col gap-2'>
|
||||
<Button
|
||||
onClick={handleSend}
|
||||
disabled={status != "ready" || !input.trim()}
|
||||
>
|
||||
Send
|
||||
</Button>
|
||||
<Button
|
||||
variant='destructive'
|
||||
onClick={handleClear}
|
||||
disabled={status != "ready" || clearChatMutation.isPending}
|
||||
>
|
||||
{clearChatMutation.isPending ?
|
||||
<Spinner /> :
|
||||
"Clear Chat"
|
||||
}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user