-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Expand file tree
/
Copy pathuse-chat.ts
More file actions
112 lines (104 loc) · 3.62 KB
/
use-chat.ts
File metadata and controls
112 lines (104 loc) · 3.62 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
import { useAtom } from 'jotai'
import { useCallback, useEffect, useMemo } from 'react'
import { Message } from '~app/components/Chat/ChatMessageInput'
import { trackEvent } from '~app/plausible'
import { chatFamily } from '~app/state'
import { setConversationMessages } from '~services/chat-history'
import { ChatMessageModel } from '~types'
import { uuid } from '~utils'
import { BotId } from '../bots'
export function useChat(botId: BotId, page = 'singleton') {
const chatAtom = useMemo(() => chatFamily({ botId, page }), [botId, page])
const [chatState, setChatState] = useAtom(chatAtom)
const updateMessage = useCallback(
(messageId: string, updater: (message: ChatMessageModel) => void) => {
setChatState((draft) => {
const message = draft.messages.find((m) => m.id === messageId)
if (message) {
updater(message)
}
})
},
[setChatState],
)
const sendMessage = useCallback(
async ({ text, role }: Message) => {
trackEvent('send_message', { botId })
const botMessageId = uuid()
setChatState((draft) => {
draft.messages.push({ id: uuid(), text, author: 'user' }, { id: botMessageId, text: '', author: botId })
})
const abortController = new AbortController()
setChatState((draft) => {
draft.generatingMessageId = botMessageId
draft.abortController = abortController
})
await chatState.bot.sendMessage({
prompt: text,
role,
signal: abortController.signal,
onEvent(event) {
if (event.type === 'UPDATE_ANSWER') {
updateMessage(botMessageId, (message) => {
message.text = event.data.text
})
} else if (event.type === 'ERROR') {
console.error('sendMessage error', event.error.code, event.error)
updateMessage(botMessageId, (message) => {
message.error = event.error
})
setChatState((draft) => {
draft.abortController = undefined
draft.generatingMessageId = ''
})
} else if (event.type === 'DONE') {
setChatState((draft) => {
draft.abortController = undefined
draft.generatingMessageId = ''
})
}
},
})
},
[botId, chatState.bot, setChatState, updateMessage],
)
const resetConversation = useCallback(() => {
chatState.bot.resetConversation()
setChatState((draft) => {
draft.abortController = undefined
draft.generatingMessageId = ''
draft.messages = []
draft.conversationId = uuid()
})
}, [chatState.bot, setChatState])
const stopGenerating = useCallback(() => {
chatState.abortController?.abort()
if (chatState.generatingMessageId) {
updateMessage(chatState.generatingMessageId, (message) => {
if (!message.text && !message.error) {
message.text = 'Cancelled'
}
})
}
setChatState((draft) => {
draft.generatingMessageId = ''
})
}, [chatState.abortController, chatState.generatingMessageId, setChatState, updateMessage])
useEffect(() => {
if (chatState.messages.length) {
setConversationMessages(botId, chatState.conversationId, chatState.messages)
}
}, [botId, chatState.conversationId, chatState.messages])
const chat = useMemo(
() => ({
botId,
messages: chatState.messages,
sendMessage,
resetConversation,
generating: !!chatState.generatingMessageId,
stopGenerating,
}),
[botId, chatState.generatingMessageId, chatState.messages, resetConversation, sendMessage, stopGenerating],
)
return chat
}