import type { ChatMessage } from '@/types'
import { createSignal, Index, Show } from 'solid-js'
import IconClear from './icons/Clear'
import MessageItem from './MessageItem'
import SystemRoleSettings from './SystemRoleSettings'
import _ from 'lodash'
import { generateSignature } from '@/utils/auth'
export default () => {
let inputRef: HTMLTextAreaElement
const [currentSystemRoleSettings, setCurrentSystemRoleSettings] = createSignal('')
const [systemRoleEditing, setSystemRoleEditing] = createSignal(false)
const [messageList, setMessageList] = createSignal([])
const [currentAssistantMessage, setCurrentAssistantMessage] = createSignal('')
const [loading, setLoading] = createSignal(false)
const [controller, setController] = createSignal(null)
const handleButtonClick = async () => {
const inputValue = inputRef.value
if (!inputValue) {
return
}
// @ts-ignore
if (window?.umami) umami.trackEvent('chatgenerate')
inputRef.value = ''
setMessageList([
...messageList(),
{
role: 'user',
content: inputValue,
},
])
requestWithLatestMessage()
}
const throttle = .throttle(function(){
window.scrollTo({top: document.body.scrollHeight, behavior: 'smooth'})
}, 300, {
leading: true,
trailing: false
})
const requestWithLatestMessage = async () => {
setLoading(true)
setCurrentAssistantMessage('')
const storagePassword = localStorage.getItem('pass')
try {
const controller = new AbortController()
setController(controller)
const requestMessageList = [...messageList()]
if (currentSystemRoleSettings()) {
requestMessageList.unshift({
role: 'system',
content: currentSystemRoleSettings(),
})
}
const timestamp = Date.now()
const response = await fetch('/api/generate', {
method: 'POST',
body: JSON.stringify({
messages: requestMessageList,
time: timestamp,
pass: storagePassword,
sign: await generateSignature({
t: timestamp,
m: requestMessageList?.[requestMessageList.length - 1]?.content || '',
}),
}),
signal: controller.signal,
})
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)
}
throttle()
}
done = readerDone
}
} catch (e) {
console.error(e)
setLoading(false)
setController(null)
return
}
archiveCurrentMessage()
}
const archiveCurrentMessage = () => {
if (currentAssistantMessage()) {
setMessageList([
...messageList(),
{
role: 'assistant',
content: currentAssistantMessage(),
},
])
setCurrentAssistantMessage('')
setLoading(false)
setController(null)
inputRef.focus()
}
}
const clear = () => {
inputRef.value = ''
inputRef.style.height = 'auto';
setMessageList([])
setCurrentAssistantMessage('')
setCurrentSystemRoleSettings('')
}
const stopStreamFetch = () => {
if (controller()) {
controller().abort()
archiveCurrentMessage()
}
}
const retryLastFetch = () => {
if (messageList().length > 0) {
const lastMessage = messageList()[messageList().length - 1]
console.log(lastMessage)
if (lastMessage.role === 'assistant') {
setMessageList(messageList().slice(0, -1))
requestWithLatestMessage()
}
}
}
const handleKeydown = (e: KeyboardEvent) => {
if (e.isComposing || e.shiftKey) {
return
}
if (e.key === 'Enter') {
handleButtonClick()
}
}
return (
canEdit={() => messageList().length === 0}
systemRoleEditing={systemRoleEditing}
setSystemRoleEditing={setSystemRoleEditing}
currentSystemRoleSettings={currentSystemRoleSettings}
setCurrentSystemRoleSettings={setCurrentSystemRoleSettings}
/>
{(message, index) => (
role={message().role}
message={message().content}
showRetry={() => (message().role === 'assistant' && index === messageList().length - 1)}
onRetry={retryLastFetch}
/>
)}
{currentAssistantMessage() && (
role="assistant"
message={currentAssistantMessage}
/>
)}
when={!loading()}
fallback={() => (
)}
ref={inputRef!}
disabled={systemRoleEditing()}
onKeyDown={handleKeydown}
placeholder="问些问题吧..."
autocomplete="off"
autofocus
onInput={() => {
inputRef.style.height = 'auto';
inputRef.style.height = inputRef.scrollHeight + 'px';
}}
rows="1"
w-full
px-3 py-3
min-h-12
max-h-36
rounded-sm
bg-slate
bg-op-15
resize-none
focus:bg-op-20
focus:ring-0
focus:outline-none
placeholder:op-50
dark="placeholder:op-30"
scroll-pa-8px
/>
Send
)
}