zss2341's picture
Upload 22 files
b06ed0c
import { createSignal, For, Show } from 'solid-js'
import MessageItem from './MessageItem'
import IconClear from './icons/Clear'
import type { ChatMessage } from '../types'
export default () => {
let inputRef: HTMLInputElement
const [messageList, setMessageList] = createSignal<ChatMessage[]>([])
const [currentAssistantMessage, setCurrentAssistantMessage] = createSignal('')
const [loading, setLoading] = createSignal(false)
const handleButtonClick = async () => {
const inputValue = inputRef.value
if (!inputValue) {
return
}
setLoading(true)
// @ts-ignore
if (window?.umami) umami.trackEvent('chat_generate')
inputRef.value = ''
setMessageList([
...messageList(),
{
role: 'user',
content: inputValue,
},
])
const response = await fetch('/api/generate', {
method: 'POST',
body: JSON.stringify({
messages: messageList(),
}),
})
if (!response.ok) {
throw new Error(response.statusText)
}
const data = response.body
if (!data) {
throw new Error('No data')
}
const reader = data.getReader()
const decoder = new TextDecoder('utf-8')
let done = false
while (!done) {
const { value, done: readerDone } = await reader.read()
if (value) {
let char = decoder.decode(value)
if (char === '\n' && currentAssistantMessage().endsWith('\n')) {
continue
}
if (char) {
setCurrentAssistantMessage(currentAssistantMessage() + char)
}
}
done = readerDone
}
setMessageList([
...messageList(),
{
role: 'assistant',
content: currentAssistantMessage(),
},
])
setCurrentAssistantMessage('')
setLoading(false)
}
const clear = () => {
inputRef.value = ''
setMessageList([])
setCurrentAssistantMessage('')
}
return (
<div my-6>
<For each={messageList()}>{(message) => <MessageItem role={message.role} message={message.content} />}</For>
{ currentAssistantMessage() && <MessageItem role="assistant" message={currentAssistantMessage} /> }
<Show when={!loading()} fallback={() => <div class="h-12 my-4 flex items-center justify-center bg-slate bg-op-15 text-slate rounded-sm">AI is thinking...</div>}>
<div class="my-4 flex items-center gap-2">
<input
ref={inputRef!}
type="text"
id="input"
placeholder="Enter something..."
autocomplete='off'
autofocus
disabled={loading()}
onKeyDown={(e) => {
e.key === 'Enter' && !e.isComposing && handleButtonClick()
}}
w-full
px-4
h-12
text-slate
rounded-sm
bg-slate
bg-op-15
focus:bg-op-20
focus:ring-0
focus:outline-none
placeholder:text-slate-400
placeholder:op-30
/>
<button onClick={handleButtonClick} disabled={loading()} h-12 px-4 py-2 bg-slate bg-op-15 hover:bg-op-20 text-slate rounded-sm>
Send
</button>
<button title='Clear' onClick={clear} disabled={loading()} h-12 px-4 py-2 bg-slate bg-op-15 hover:bg-op-20 text-slate rounded-sm>
<IconClear />
</button>
</div>
</Show>
</div>
)
}