129 lines
4.1 KiB
TypeScript
129 lines
4.1 KiB
TypeScript
import { useEffect, useState, useRef } from 'react'
|
|
import type { AI } from '@/app/[locale]/morphic/action'
|
|
import { useUIState, useActions, useAIState } from '@aigxion/isdk/rsc'
|
|
import { cn } from '@/lib/utils'
|
|
import { UserMessage } from './user-message'
|
|
import { Input } from '../ui-v2/input'
|
|
import { Button } from '../ui-v2/button'
|
|
import { ArrowRight, Plus, Square } from 'lucide-react'
|
|
import { EmptyScreen } from './empty-screen'
|
|
|
|
export function ChatPanel() {
|
|
const [input, setInput] = useState('')
|
|
const [messages, setMessages] = useUIState<typeof AI>()
|
|
const [aiMessages, setAiMessages] = useAIState<typeof AI>()
|
|
const { submit } = useActions<typeof AI>()
|
|
const [isButtonPressed, setIsButtonPressed] = useState(false)
|
|
const inputRef = useRef<HTMLInputElement>(null)
|
|
const [showEmptyScreen, setShowEmptyScreen] = useState(false)
|
|
// Focus on input when button is pressed
|
|
useEffect(() => {
|
|
if (isButtonPressed) {
|
|
inputRef.current?.focus()
|
|
setIsButtonPressed(false)
|
|
}
|
|
}, [isButtonPressed])
|
|
|
|
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
|
e.preventDefault()
|
|
|
|
// Clear messages if button is pressed
|
|
if (isButtonPressed) {
|
|
handleClear()
|
|
setIsButtonPressed(false)
|
|
}
|
|
|
|
// Add user message to UI state
|
|
setMessages(currentMessages => [
|
|
...currentMessages,
|
|
{
|
|
id: Date.now(),
|
|
isGenerating: false,
|
|
component: <UserMessage message={input} />
|
|
}
|
|
])
|
|
|
|
// Submit and get response message
|
|
const formData = new FormData(e.currentTarget)
|
|
const responseMessage = await submit(formData)
|
|
setMessages(currentMessages => [...currentMessages, responseMessage as any])
|
|
|
|
setInput('')
|
|
}
|
|
|
|
// Clear messages
|
|
const handleClear = () => {
|
|
setIsButtonPressed(true)
|
|
setMessages([])
|
|
setAiMessages([])
|
|
}
|
|
|
|
useEffect(() => {
|
|
// focus on input when the page loads
|
|
inputRef.current?.focus()
|
|
}, [])
|
|
|
|
// If there are messages and the new button has not been pressed, display the new Button
|
|
if (messages.length > 0 && !isButtonPressed) {
|
|
return (
|
|
<div className="fixed bottom-2 md:bottom-8 left-0 right-0 flex justify-center items-center mx-auto">
|
|
<Button
|
|
type="button"
|
|
variant={'secondary'}
|
|
className="rounded-full bg-secondary/80 group transition-all hover:scale-105"
|
|
onClick={() => handleClear()}
|
|
>
|
|
<span className="text-sm mr-2 group-hover:block hidden animate-in fade-in duration-300">
|
|
New
|
|
</span>
|
|
<Plus size={18} className="group-hover:rotate-90 transition-all" />
|
|
</Button>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
// Condition 1 and 3: If there are no messages or the button is pressed, display the form
|
|
const formPositionClass =
|
|
messages.length === 0
|
|
? 'fixed bottom-8 left-0 right-0 top-10 mx-auto h-screen flex flex-col items-center justify-center'
|
|
: 'fixed bottom-8-ml-6'
|
|
return (
|
|
<div className={formPositionClass}>
|
|
{/* <IconKuroko className="w-6 h-6 mb-4" /> */}
|
|
<form onSubmit={handleSubmit} className="max-w-2xl w-full px-6">
|
|
<div className="relative flex items-center w-full">
|
|
<Input
|
|
ref={inputRef}
|
|
type="text"
|
|
name="input"
|
|
placeholder="Ask a question..."
|
|
value={input}
|
|
className="pl-4 pr-10 h-12 rounded-full bg-muted"
|
|
onChange={e => {
|
|
setInput(e.target.value)
|
|
setShowEmptyScreen(e.target.value.length === 0)
|
|
}}
|
|
onFocus={() => setShowEmptyScreen(true)}
|
|
onBlur={() => setShowEmptyScreen(false)}
|
|
/>
|
|
<Button
|
|
type="submit"
|
|
size={'icon'}
|
|
variant={'ghost'}
|
|
className="absolute right-2 top-1/2 transform -translate-y-1/2"
|
|
disabled={input.length === 0}
|
|
>
|
|
<ArrowRight size={20} />
|
|
</Button>
|
|
</div>
|
|
<EmptyScreen
|
|
submitMessage={message => {
|
|
setInput(message)
|
|
}}
|
|
className={cn(showEmptyScreen ? 'visible' : 'invisible')}
|
|
/>
|
|
</form>
|
|
</div>
|
|
)
|
|
}
|