kangfenmao 82e9baf211 fix: Improved user experience by adding timeout to text area resize.
- Added timeout before resizing text area to improve user experience.
- Removed import of the unused `useProviderByAssistant` hook.
2024-09-16 13:03:29 +08:00

172 lines
6.0 KiB
TypeScript

import db from '@renderer/databases'
import { useAssistant } from '@renderer/hooks/useAssistant'
import { getTopic, TopicManager } from '@renderer/hooks/useTopic'
import { fetchChatCompletion, fetchMessagesSummary } from '@renderer/services/api'
import { getDefaultTopic } from '@renderer/services/assistant'
import { EVENT_NAMES, EventEmitter } from '@renderer/services/event'
import {
deleteMessageFiles,
estimateHistoryTokenCount,
filterMessages,
getContextCount
} from '@renderer/services/messages'
import { Assistant, Message, Model, Topic } from '@renderer/types'
import { getBriefInfo, runAsyncFunction, uuid } from '@renderer/utils'
import { t } from 'i18next'
import { last, reverse, take } from 'lodash'
import { FC, useCallback, useEffect, useRef, useState } from 'react'
import styled from 'styled-components'
import Suggestions from '../components/Suggestions'
import MessageItem from './Message'
import Prompt from './Prompt'
interface Props {
assistant: Assistant
topic: Topic
setActiveTopic: (topic: Topic) => void
}
const Messages: FC<Props> = ({ assistant, topic, setActiveTopic }) => {
const [messages, setMessages] = useState<Message[]>([])
const [lastMessage, setLastMessage] = useState<Message | null>(null)
const containerRef = useRef<HTMLDivElement>(null)
const { updateTopic, addTopic } = useAssistant(assistant.id)
const onSendMessage = useCallback(
(message: Message) => {
const _messages = [...messages, message]
setMessages(_messages)
db.topics.put({ id: topic.id, messages: _messages })
},
[messages, topic]
)
const autoRenameTopic = useCallback(async () => {
const _topic = getTopic(assistant, topic.id)
if (_topic && _topic.name === t('chat.default.topic.name') && messages.length >= 2) {
const summaryText = await fetchMessagesSummary({ messages, assistant })
if (summaryText) {
const data = { ..._topic, name: summaryText }
setActiveTopic(data)
updateTopic(data)
}
}
}, [assistant, messages, setActiveTopic, topic.id, updateTopic])
const onDeleteMessage = useCallback(
(message: Message) => {
const _messages = messages.filter((m) => m.id !== message.id)
setMessages(_messages)
db.topics.update(topic.id, { messages: _messages })
deleteMessageFiles(message)
},
[messages, topic.id]
)
useEffect(() => {
const unsubscribes = [
EventEmitter.on(EVENT_NAMES.SEND_MESSAGE, async (msg: Message) => {
onSendMessage(msg)
fetchChatCompletion({ assistant, messages: [...messages, msg], topic, onResponse: setLastMessage })
}),
EventEmitter.on(EVENT_NAMES.AI_CHAT_COMPLETION, async (msg: Message) => {
setLastMessage(null)
onSendMessage(msg)
setTimeout(() => EventEmitter.emit(EVENT_NAMES.AI_AUTO_RENAME), 100)
}),
EventEmitter.on(EVENT_NAMES.REGENERATE_MESSAGE, async (model: Model) => {
const lastUserMessage = last(filterMessages(messages).filter((m) => m.role === 'user'))
if (lastUserMessage) {
const content = `[@${model.name}](#) ${getBriefInfo(lastUserMessage.content)}`
onSendMessage({ ...lastUserMessage, id: uuid(), type: '@', content })
fetchChatCompletion({
assistant,
topic,
messages: [...messages, lastUserMessage],
onResponse: setLastMessage
})
}
}),
EventEmitter.on(EVENT_NAMES.AI_AUTO_RENAME, autoRenameTopic),
EventEmitter.on(EVENT_NAMES.CLEAR_MESSAGES, () => {
setMessages([])
updateTopic({ ...topic, messages: [] })
TopicManager.clearTopicMessages(topic.id)
}),
EventEmitter.on(EVENT_NAMES.NEW_CONTEXT, () => {
const lastMessage = last(messages)
if (lastMessage && lastMessage.type === 'clear') {
return
}
if (messages.length === 0) {
return
}
onSendMessage({
id: uuid(),
assistantId: assistant.id,
role: 'user',
content: '',
topicId: topic.id,
createdAt: new Date().toISOString(),
status: 'success',
type: 'clear'
} as Message)
}),
EventEmitter.on(EVENT_NAMES.NEW_BRANCH, async (index: number) => {
const _topic = getDefaultTopic()
_topic.name = topic.name
await db.topics.add({ id: _topic.id, messages: take(messages, messages.length - index) })
addTopic(_topic)
setActiveTopic(_topic)
autoRenameTopic()
})
]
return () => unsubscribes.forEach((unsub) => unsub())
}, [addTopic, assistant, autoRenameTopic, messages, onSendMessage, setActiveTopic, topic, updateTopic])
useEffect(() => {
runAsyncFunction(async () => {
const messages = (await TopicManager.getTopicMessages(topic.id)) || []
setMessages(messages)
})
}, [topic.id])
useEffect(() => {
setTimeout(() => containerRef.current?.scrollTo({ top: containerRef.current.scrollHeight, behavior: 'auto' }), 0)
}, [messages])
useEffect(() => {
EventEmitter.emit(EVENT_NAMES.ESTIMATED_TOKEN_COUNT, {
tokensCount: estimateHistoryTokenCount(assistant, messages),
contextCount: getContextCount(assistant, messages)
})
}, [assistant, messages])
return (
<Container id="messages" key={assistant.id} ref={containerRef}>
<Suggestions assistant={assistant} messages={messages} lastMessage={lastMessage} />
{lastMessage && <MessageItem key={lastMessage.id} message={lastMessage} />}
{reverse([...messages]).map((message, index) => (
<MessageItem key={message.id} message={message} showMenu index={index} onDeleteMessage={onDeleteMessage} />
))}
<Prompt assistant={assistant} key={assistant.prompt} />
</Container>
)
}
const Container = styled.div`
position: relative;
display: flex;
flex-direction: column;
overflow-y: auto;
flex-direction: column-reverse;
max-height: calc(100vh - var(--input-bar-height) - var(--navbar-height));
padding: 10px 0;
`
export default Messages