Spaces:
Configuration error
Configuration error
| 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> | |
| ) | |
| } | |