6-structured_outputs/structured-outputs-assistant-starting-point/components/chat.tsx (116 lines of code) (raw):
'use client'
import React, { useRef, useEffect, useState, useMemo } from 'react'
import Message from './message'
import { Spinner } from './ui/spinner' // Assuming Spinner is imported from somewhere
import ToolResults from './tool-results'
export interface MessageProps {
content: string | React.ReactNode
role: 'user' | 'agent' | 'assistant' | 'tool'
hidden?: boolean
[key: string]: any
}
interface ChatProps {
messages: MessageProps[]
onSendMessage: (message: string) => void
loading: boolean
tool: any
}
const Chat: React.FC<ChatProps> = ({
messages,
onSendMessage,
loading,
tool
}) => {
const messagesEndRef = useRef<HTMLDivElement>(null)
const [inputMessage, setInputMessage] = useState('')
const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' })
}
useEffect(() => {
scrollToBottom()
}, [messages])
return (
<div className="flex justify-center items-center size-full">
<div className="flex grow flex-col h-full max-w-[700px] gap-2">
<div className="flex-1 overflow-y-scroll px-4">
<div className="space-y-1 pt-4">
{messages.map((message, index) => (
<React.Fragment key={index}>
{message.role === 'tool' ? (
<>
{index === messages.length - 1 && loading ? (
<div className="h-[50vh] flex justify-center items-center w-full">
<Spinner />
</div>
) : (
<ToolResults tool={message} />
)}
</>
) : (
<Message
view="user"
role={message.role === 'user' ? 'me' : 'other'}
content={message.content}
/>
)}
</React.Fragment>
))}
<div ref={messagesEndRef} />
</div>
</div>
<div className=" p-4">
<div className="flex items-center">
<div className="flex w-full items-center">
<div className="flex w-full flex-col gap-1.5 rounded-[26px] p-1.5 transition-colors bg-[#f4f4f4]">
<div className="flex items-end gap-1.5 md:gap-2 pl-4">
<div className="flex min-w-0 flex-1 flex-col">
<textarea
id="prompt-textarea"
tabIndex={0}
dir="auto"
rows={1}
placeholder="Send message"
className="m-0 resize-none border-0 focus:outline-none text-sm bg-transparent px-0 py-2 max-h-[20dvh]"
value={inputMessage}
onChange={e => setInputMessage(e.target.value)}
onKeyDown={e => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault()
onSendMessage(inputMessage)
setInputMessage('')
}
}}
/>
</div>
<button
disabled={!inputMessage}
data-testid="send-button"
className="mb-1 me-1 flex size-8 items-center justify-center rounded-full bg-black text-white transition-colors hover:opacity-70 focus-visible:outline-none focus-visible:outline-black disabled:bg-[#D7D7D7] disabled:text-[#f4f4f4] disabled:hover:opacity-100"
onClick={() => {
onSendMessage(inputMessage)
setInputMessage('')
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="32"
height="32"
fill="none"
viewBox="0 0 32 32"
className="icon-2xl"
>
<path
fill="currentColor"
fillRule="evenodd"
d="M15.192 8.906a1.143 1.143 0 0 1 1.616 0l5.143 5.143a1.143 1.143 0 0 1-1.616 1.616l-3.192-3.192v9.813a1.143 1.143 0 0 1-2.286 0v-9.813l-3.192 3.192a1.143 1.143 0 1 1-1.616-1.616z"
clipRule="evenodd"
/>
</svg>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
)
}
export default Chat