refactor: clean up MessageMenubar and enhance message handling

- Removed unused lodash dependency and optimized message resend logic in MessageMenubar.
- Streamlined Popconfirm component for message deletion.
- Updated NewTopicButton styling for improved layout.
- Enhanced messages slice to ensure better state management and error handling.
This commit is contained in:
kangfenmao 2025-03-15 18:39:51 +08:00
parent cef32f4b36
commit 730b03cde8
3 changed files with 43 additions and 39 deletions

View File

@ -30,7 +30,6 @@ import {
} from '@renderer/utils/export'
import { Button, Dropdown, Popconfirm, Tooltip } from 'antd'
import dayjs from 'dayjs'
import { isEmpty } from 'lodash'
import { FC, memo, useCallback, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
@ -55,7 +54,6 @@ const MessageMenubar: FC<Props> = (props) => {
const [isTranslating, setIsTranslating] = useState(false)
const assistantModel = assistant?.model
const {
messages,
loading,
editMessage,
setStreamMessage,
@ -87,18 +85,10 @@ const MessageMenubar: FC<Props> = (props) => {
const handleResendUserMessage = useCallback(
async (messageUpdate?: Message) => {
if (!loading) {
const groupdMessages = messages.filter((m) => m.askId === message.id)
// Resend all grouped messages
if (!isEmpty(groupdMessages)) {
await resendMessage(message, assistant)
return
}
await resendMessage(messageUpdate ?? message, assistant)
}
},
[message, resendMessage, assistant, messages, loading]
[assistant, loading, message, resendMessage]
)
const onEdit = useCallback(async () => {
@ -331,19 +321,17 @@ const MessageMenubar: FC<Props> = (props) => {
</ActionButton>
</Tooltip>
)}
{!isGrouped && (
<Popconfirm
title={t('message.message.delete.content')}
okButtonProps={{ danger: true }}
icon={<QuestionCircleOutlined style={{ color: 'red' }} />}
onConfirm={() => deleteMessage(message)}>
<ActionButton className="message-action-button" onClick={(e) => e.stopPropagation()}>
<Tooltip title={t('common.delete')} mouseEnterDelay={1}>
<DeleteOutlined />
</Tooltip>
</ActionButton>
</Popconfirm>
)}
<Popconfirm
title={t('message.message.delete.content')}
okButtonProps={{ danger: true }}
icon={<QuestionCircleOutlined style={{ color: 'red' }} />}
onConfirm={() => deleteMessage(message)}>
<ActionButton className="message-action-button" onClick={(e) => e.stopPropagation()}>
<Tooltip title={t('common.delete')} mouseEnterDelay={1}>
<DeleteOutlined />
</Tooltip>
</ActionButton>
</Popconfirm>
{!isUserMessage && (
<Dropdown
menu={{ items: dropdownItems, onClick: (e) => e.domEvent.stopPropagation() }}

View File

@ -29,6 +29,8 @@ const Container = styled.div`
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 10px;
margin-top: -10px;
`
const Button = styled(AntdButton)<{ $theme: ThemeMode }>`

View File

@ -9,7 +9,7 @@ import type { AppDispatch, RootState } from '@renderer/store'
import type { Assistant, Message, Topic } from '@renderer/types'
import { Model } from '@renderer/types'
import { clearTopicQueue, getTopicQueue, waitForTopicQueue } from '@renderer/utils/queue'
import { cloneDeep, throttle } from 'lodash'
import { cloneDeep, isEmpty, throttle } from 'lodash'
export interface MessagesState {
messagesByTopic: Record<string, Message[]>
@ -95,9 +95,11 @@ const messagesSlice = createSlice({
},
addMessage: (state, action: PayloadAction<{ topicId: string; messages: Message | Message[] }>) => {
const { topicId, messages } = action.payload
if (!state.messagesByTopic[topicId]) {
state.messagesByTopic[topicId] = []
}
if (Array.isArray(messages)) {
// 为了兼容多模型新发消息,一次性添加多个助手消息
// 不是什么好主意,不符合语义
@ -112,6 +114,7 @@ const messagesSlice = createSlice({
action: PayloadAction<{ topicId: string; messages: Message | Message[]; position?: number }>
) => {
const { topicId, messages, position } = action.payload
if (!state.messagesByTopic[topicId]) {
state.messagesByTopic[topicId] = []
}
@ -136,6 +139,7 @@ const messagesSlice = createSlice({
) => {
const { topicId, messageId, updates } = action.payload
const topicMessages = state.messagesByTopic[topicId]
if (topicMessages) {
const message = topicMessages.find((msg) => msg.id === messageId)
if (message) {
@ -160,9 +164,11 @@ const messagesSlice = createSlice({
},
setStreamMessage: (state, action: PayloadAction<{ topicId: string; message: Message | null }>) => {
const { topicId, message } = action.payload
if (!state.streamMessagesByTopic[topicId]) {
state.streamMessagesByTopic[topicId] = {}
}
if (message) {
state.streamMessagesByTopic[topicId][message.id] = message
}
@ -193,11 +199,13 @@ const messagesSlice = createSlice({
// 添加新消息
state.messagesByTopic[topicId].push(streamMessage)
}
// 删除流状态
delete state.streamMessagesByTopic[topicId][messageId]
},
clearStreamMessage: (state, action: PayloadAction<{ topicId: string; messageId: string }>) => {
const { topicId, messageId } = action.payload
if (state.streamMessagesByTopic[topicId]) {
delete state.streamMessagesByTopic[topicId][messageId]
}
@ -231,6 +239,7 @@ const handleResponseMessageUpdate = (
if (message.status === 'success') {
autoRenameTopic(assistant, topicId)
}
if (message.status !== 'sending') {
dispatch(commitStreamMessage({ topicId, messageId: message.id }))
const state = getState()
@ -246,9 +255,7 @@ const handleResponseMessageUpdate = (
const syncMessagesWithDB = async (topicId: string, messages: Message[]) => {
const topic = await db.topics.get(topicId)
if (topic) {
await db.topics.update(topicId, {
messages
})
await db.topics.update(topicId, { messages })
} else {
await db.topics.add({ id: topicId, messages })
}
@ -281,12 +288,13 @@ export const sendMessage =
// 处理助手消息
let assistantMessages: Message[] = []
if (options?.resendAssistantMessage) {
if (!isEmpty(options?.resendAssistantMessage)) {
// 直接使用传入的助手消息,进行重置
const messageToReset = options.resendAssistantMessage
if (Array.isArray(messageToReset)) {
assistantMessages = messageToReset.map((m) => {
const resetMessage = resetAssistantMessage(m, assistant.model)
const isGroupedMessage = messageToReset.length > 1
const resetMessage = resetAssistantMessage(m, isGroupedMessage ? m.model : assistant.model)
// 更新状态
dispatch(updateMessage({ topicId: topic.id, messageId: m.id, updates: resetMessage }))
// 使用重置后的消息
@ -324,18 +332,27 @@ export const sendMessage =
// 最后一个具有相同askId的助手消息在其后插入
let position: number | undefined
if (options?.isMentionModel) {
// 寻找用户提问对应的助手回答消息位置
const lastAssistantIndex = currentMessages.findLastIndex(
(m) => m.role === 'assistant' && m.askId === userMessage.id
)
// 如果找到了助手消息,在助手消息后插入
if (lastAssistantIndex !== -1) {
position = lastAssistantIndex + 1
} else {
// 如果找不到助手消息,则在用户消息后插入
const userMessageIndex = currentMessages.findIndex((m) => m.role === 'user' && m.id === userMessage.id)
if (userMessageIndex !== -1) {
position = userMessageIndex + 1
}
}
}
dispatch(
appendMessage({
topicId: topic.id,
messages: !options?.isMentionModel ? [userMessage, ...assistantMessages] : assistantMessages,
messages: options?.isMentionModel ? assistantMessages : [userMessage, ...assistantMessages],
position
})
)
@ -393,15 +410,10 @@ export const sendMessage =
return messagesUpToUser.filter((m) => !m.status?.includes('ing'))
}
// 如果找不到对应的用户消息,使用原有逻辑
// 按理说不会找不到 先注释掉看看
// if (messageIndex !== -1) {
// const messagesUpToAssistant = messages.slice(0, messageIndex)
// return messagesUpToAssistant.filter((m) => !m.status?.includes('ing'))
// }
// 没有找到消息索引的情况,过滤所有消息
return messages.filter((m) => !m.status?.includes('ing'))
}
await fetchChatCompletion({
message: { ...assistantMessage },
messages: handleMessages(),
@ -461,12 +473,14 @@ export const resendMessage =
if (message.role === 'user') {
// 查找此用户消息对应的助手消息
const assistantMessage = topicMessages.filter((m) => m.role === 'assistant' && m.askId === message.id)
return dispatch(
sendMessage(message, assistant, topic, {
resendAssistantMessage: assistantMessage,
// 用户可能把助手消息删了,然后重新发送用户消息
// 如果isMentionModel为false,则只会发送add助手消息
isMentionModel: !assistantMessage
// 如果 isMentionModel 为 false, 则只会发送 add 助手消息
isMentionModel: isEmpty(assistantMessage),
mentions: message.mentions
})
)
}