feat: enhance multi-select functionality with message registration

This commit is contained in:
Pleasurecruise 2025-05-19 00:51:40 +08:00
parent 7ae21b6b26
commit 7b4f79aab5
No known key found for this signature in database
GPG Key ID: E6385136096279B6
3 changed files with 54 additions and 44 deletions

View File

@ -3,7 +3,7 @@ import { messageBlocksSelectors } from '@renderer/store/messageBlock'
import { newMessagesActions, selectMessagesForTopic } from '@renderer/store/newMessage'
import { Topic } from '@renderer/types'
import { Modal } from 'antd'
import { createContext, FC, ReactNode, use, useState } from 'react'
import { createContext, FC, ReactNode, use, useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useDispatch, useSelector } from 'react-redux'
@ -12,6 +12,8 @@ interface ChatContextProps {
toggleMultiSelectMode: (value: boolean) => void
handleMultiSelectAction: (actionType: string, messageIds: string[]) => void
activeTopic: Topic
messageRefs: Map<string, HTMLElement>
registerMessageElement: (id: string, element: HTMLElement | null) => void
}
const ChatContext = createContext<ChatContextProps | undefined>(undefined)
@ -35,6 +37,7 @@ export const ChatProvider: FC<ChatProviderProps> = ({ children, activeTopic }) =
const [isMultiSelectMode, setIsMultiSelectMode] = useState(false)
const [confirmDeleteVisible, setConfirmDeleteVisible] = useState(false)
const [messagesToDelete, setMessagesToDelete] = useState<string[]>([])
const [messageRefs, setMessageRefs] = useState<Map<string, HTMLElement>>(new Map())
const messages = useSelector((state: RootState) => selectMessagesForTopic(state, activeTopic.id))
const messageBlocks = useSelector(messageBlocksSelectors.selectEntities)
@ -43,6 +46,18 @@ export const ChatProvider: FC<ChatProviderProps> = ({ children, activeTopic }) =
setIsMultiSelectMode(value)
}
const registerMessageElement = useCallback((id: string, element: HTMLElement | null) => {
setMessageRefs((prev) => {
const newRefs = new Map(prev)
if (element) {
newRefs.set(id, element)
} else {
newRefs.delete(id)
}
return newRefs
})
}, [])
const handleMultiSelectAction = (actionType: string, messageIds: string[]) => {
if (messageIds.length === 0) {
window.message.warning(t('chat.multiple.select.empty'))
@ -134,7 +149,9 @@ export const ChatProvider: FC<ChatProviderProps> = ({ children, activeTopic }) =
isMultiSelectMode,
toggleMultiSelectMode,
handleMultiSelectAction,
activeTopic
activeTopic,
messageRefs,
registerMessageElement
}
return (

View File

@ -1,7 +1,6 @@
import Scrollbar from '@renderer/components/Scrollbar'
import { useMessageOperations } from '@renderer/hooks/useMessageOperations'
import { useSettings } from '@renderer/hooks/useSettings'
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
import { MultiModelMessageStyle } from '@renderer/store/settings'
import type { Topic } from '@renderer/types'
import type { Message } from '@renderer/types/newMessage'
@ -10,6 +9,7 @@ import { Popover } from 'antd'
import { memo, useCallback, useEffect, useRef, useState } from 'react'
import styled, { css } from 'styled-components'
import { useChatContext } from './ChatContext'
import MessageItem from './Message'
import MessageGroupMenuBar from './MessageGroupMenuBar'
import SelectableMessage from './MessageSelect'
@ -18,9 +18,9 @@ interface Props {
messages: (Message & { index: number })[]
topic: Topic
hidePresetMessages?: boolean
isMultiSelectMode?: boolean // 添加是否处于多选模式
selectedMessages?: Set<string> // 已选择的消息ID集合
onSelectMessage?: (messageId: string, selected: boolean) => void // 消息选择回调
isMultiSelectMode?: boolean
selectedMessages?: Set<string>
onSelectMessage?: (messageId: string, selected: boolean) => void
registerMessageElement?: (id: string, element: HTMLElement | null) => void
}
@ -35,6 +35,7 @@ const MessageGroup = ({
}: Props) => {
const { editMessage } = useMessageOperations(topic)
const { multiModelMessageStyle: multiModelMessageStyleSetting, gridColumns, gridPopoverTrigger } = useSettings()
const { registerMessageElement: contextRegisterMessageElement } = useChatContext()
const [multiModelMessageStyle, setMultiModelMessageStyle] = useState<MultiModelMessageStyle>(
messages[0].multiModelMessageStyle || multiModelMessageStyleSetting
@ -123,41 +124,20 @@ const MessageGroup = ({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [messages, selectedIndex, isGrouped, messageLength])
// 添加对LOCATE_MESSAGE事件的监听
useEffect(() => {
// 为每个消息注册一个定位事件监听器
const eventHandlers: { [key: string]: () => void } = {}
messages.forEach((message) => {
const eventName = EVENT_NAMES.LOCATE_MESSAGE + ':' + message.id
const handler = () => {
// 检查消息是否处于可见状态
const element = document.getElementById(`message-${message.id}`)
if (element) {
const display = window.getComputedStyle(element).display
if (display === 'none') {
// 如果消息隐藏,先切换标签
setSelectedMessage(message)
} else {
// 直接滚动
element.scrollIntoView({ behavior: 'smooth', block: 'start' })
}
}
const element = document.getElementById(`message-${message.id}`)
if (element) {
contextRegisterMessageElement(message.id, element)
}
eventHandlers[eventName] = handler
EventEmitter.on(eventName, handler)
})
// 清理函数
return () => {
// 移除所有事件监听器
Object.entries(eventHandlers).forEach(([eventName, handler]) => {
EventEmitter.off(eventName, handler)
messages.forEach((message) => {
contextRegisterMessageElement(message.id, null)
})
}
}, [messages, setSelectedMessage])
}, [messages, contextRegisterMessageElement])
const renderMessage = useCallback(
(message: Message & { index: number }, index: number) => {
@ -227,16 +207,17 @@ const MessageGroup = ({
[
isGrid,
isGrouped,
isHorizontal,
multiModelMessageStyle,
selectedIndex,
topic,
hidePresetMessages,
gridPopoverTrigger,
multiModelMessageStyle,
selectedIndex,
isHorizontal,
getSelectedMessageId,
isMultiSelectMode, // 添加依赖项
selectedMessages, // 添加依赖项
onSelectMessage // 添加依赖项
isMultiSelectMode,
selectedMessages,
registerMessageElement,
onSelectMessage,
gridPopoverTrigger
]
)

View File

@ -2,6 +2,8 @@ import { Checkbox } from 'antd'
import { FC, ReactNode, useEffect, useRef } from 'react'
import styled from 'styled-components'
import { useChatContext } from './ChatContext'
interface SelectableMessageProps {
children: ReactNode
isMultiSelectMode: boolean
@ -20,14 +22,24 @@ const SelectableMessage: FC<SelectableMessageProps> = ({
registerElement
}) => {
const containerRef = useRef<HTMLDivElement>(null)
const { registerMessageElement: contextRegister } = useChatContext()
useEffect(() => {
if (registerElement && containerRef.current) {
registerElement(messageId, containerRef.current)
return () => registerElement(messageId, null)
if (containerRef.current) {
if (registerElement) {
registerElement(messageId, containerRef.current)
}
contextRegister(messageId, containerRef.current)
return () => {
if (registerElement) {
registerElement(messageId, null)
}
contextRegister(messageId, null)
}
}
return undefined
}, [messageId, registerElement])
}, [messageId, registerElement, contextRegister])
return (
<Container ref={containerRef}>