From 17be88373ed9d439247eafb6d0fe37b0888cad60 Mon Sep 17 00:00:00 2001 From: Pleasurecruise <3196812536@qq.com> Date: Sat, 24 May 2025 13:16:20 +0800 Subject: [PATCH] fix: improve multi-select functionality in Messages and SelectionBox components --- src/renderer/src/hooks/useChatContext.ts | 6 +- .../src/pages/home/Messages/Messages.tsx | 10 +- .../src/pages/home/Messages/SelectionBox.tsx | 124 +++++++++--------- 3 files changed, 71 insertions(+), 69 deletions(-) diff --git a/src/renderer/src/hooks/useChatContext.ts b/src/renderer/src/hooks/useChatContext.ts index ae67a85413..2771037d33 100644 --- a/src/renderer/src/hooks/useChatContext.ts +++ b/src/renderer/src/hooks/useChatContext.ts @@ -80,7 +80,11 @@ export const useChatContext = (activeTopic: Topic) => { (messageId: string, selected: boolean) => { dispatch( setSelectedMessageIds( - selected ? [...selectedMessageIds, messageId] : selectedMessageIds.filter((id) => id !== messageId) + selected + ? selectedMessageIds.includes(messageId) + ? selectedMessageIds + : [...selectedMessageIds, messageId] + : selectedMessageIds.filter((id) => id !== messageId) ) ) }, diff --git a/src/renderer/src/pages/home/Messages/Messages.tsx b/src/renderer/src/pages/home/Messages/Messages.tsx index 52e4abe7b5..430060ba6d 100644 --- a/src/renderer/src/pages/home/Messages/Messages.tsx +++ b/src/renderer/src/pages/home/Messages/Messages.tsx @@ -2,11 +2,13 @@ import SvgSpinners180Ring from '@renderer/components/Icons/SvgSpinners180Ring' import Scrollbar from '@renderer/components/Scrollbar' import { LOAD_MORE_COUNT } from '@renderer/config/constant' import { useAssistant } from '@renderer/hooks/useAssistant' +import { useChatContext } from '@renderer/hooks/useChatContext' import { useMessageOperations, useTopicMessages } from '@renderer/hooks/useMessageOperations' import useScrollPosition from '@renderer/hooks/useScrollPosition' import { useSettings } from '@renderer/hooks/useSettings' import { useShortcut } from '@renderer/hooks/useShortcuts' import { autoRenameTopic, getTopic } from '@renderer/hooks/useTopic' +import SelectionBox from '@renderer/pages/home/Messages/SelectionBox' import { getDefaultTopic } from '@renderer/services/AssistantService' import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService' import { getContextCount, getGroupedMessages, getUserMessage } from '@renderer/services/MessagesService' @@ -64,7 +66,7 @@ const Messages: React.FC = ({ assistant, topic, setActiveTopic, o const { displayCount, clearTopicMessages, deleteMessage, createTopicBranch } = useMessageOperations(topic) const messagesRef = useRef(messages) - // const { isMultiSelectMode, handleSelectMessage } = useChatContext(topic) + const { isMultiSelectMode, handleSelectMessage } = useChatContext(topic) useEffect(() => { messagesRef.current = messages @@ -313,13 +315,13 @@ const Messages: React.FC = ({ assistant, topic, setActiveTopic, o {messageNavigation === 'anchor' && } {messageNavigation === 'buttons' && } - {/* TODO: 多选功能实现有问题,需要重新改改 */} - {/* */} + /> ) } diff --git a/src/renderer/src/pages/home/Messages/SelectionBox.tsx b/src/renderer/src/pages/home/Messages/SelectionBox.tsx index b8ac6206d7..ab48b69a8e 100644 --- a/src/renderer/src/pages/home/Messages/SelectionBox.tsx +++ b/src/renderer/src/pages/home/Messages/SelectionBox.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react' +import { useEffect, useRef, useState } from 'react' import styled from 'styled-components' interface SelectionBoxProps { @@ -18,6 +18,8 @@ const SelectionBox: React.FC = ({ const [dragStart, setDragStart] = useState({ x: 0, y: 0 }) const [dragCurrent, setDragCurrent] = useState({ x: 0, y: 0 }) + const dragSelectedIds = useRef>(new Set()) + useEffect(() => { if (!isMultiSelectMode) return @@ -25,96 +27,90 @@ const SelectionBox: React.FC = ({ const container = scrollContainerRef.current! if (!container) return { x: 0, y: 0 } const rect = container.getBoundingClientRect() - const x = e.clientX - rect.left + container.scrollLeft - const y = e.clientY - rect.top + container.scrollTop - return { x, y } + return { + x: e.clientX - rect.left + container.scrollLeft, + y: e.clientY - rect.top + container.scrollTop + } } const handleMouseDown = (e: MouseEvent) => { if ((e.target as HTMLElement).closest('.ant-checkbox-wrapper')) return if ((e.target as HTMLElement).closest('.MessageFooter')) return + + e.preventDefault() + setIsDragging(true) const pos = updateDragPos(e) setDragStart(pos) setDragCurrent(pos) + dragSelectedIds.current.clear() document.body.classList.add('no-select') } const handleMouseMove = (e: MouseEvent) => { if (!isDragging) return - setDragCurrent(updateDragPos(e)) - const container = scrollContainerRef.current! - if (container) { - const { top, bottom } = container.getBoundingClientRect() - const scrollSpeed = 15 - if (e.clientY < top + 50) { - container.scrollBy(0, -scrollSpeed) - } else if (e.clientY > bottom - 50) { - container.scrollBy(0, scrollSpeed) + + e.preventDefault() + + const pos = updateDragPos(e) + setDragCurrent(pos) + + // 计算当前框选矩形 + const left = Math.min(dragStart.x, pos.x) + const right = Math.max(dragStart.x, pos.x) + const top = Math.min(dragStart.y, pos.y) + const bottom = Math.max(dragStart.y, pos.y) + + // 创建新选中的消息ID集合 + const newSelectedIds = new Set() + + messageElements.forEach((el, id) => { + // 检查消息是否已被选中(不管是拖动选中还是手动选中) + const checkbox = el.querySelector('input[type="checkbox"]') as HTMLInputElement | null + const isAlreadySelected = checkbox?.checked || false + + // 如果已经被记录为拖动选中,跳过 + if (dragSelectedIds.current.has(id)) return + + const rect = el.getBoundingClientRect() + const container = scrollContainerRef.current! + const eTop = rect.top - container.getBoundingClientRect().top + container.scrollTop + const eLeft = rect.left - container.getBoundingClientRect().left + container.scrollLeft + const eBottom = eTop + rect.height + const eRight = eLeft + rect.width + + // 检查消息是否在当前选择框内 + const isInSelectionBox = !(eRight < left || eLeft > right || eBottom < top || eTop > bottom) + + // 只有在选择框内且未被选中的消息才需要处理 + if (isInSelectionBox && !isAlreadySelected) { + handleSelectMessage(id, true) + dragSelectedIds.current.add(id) + newSelectedIds.add(id) + el.classList.add('selection-highlight') + setTimeout(() => el.classList.remove('selection-highlight'), 300) } - } + }) } const handleMouseUp = () => { if (!isDragging) return - - const left = Math.min(dragStart.x, dragCurrent.x) - const right = Math.max(dragStart.x, dragCurrent.x) - const top = Math.min(dragStart.y, dragCurrent.y) - const bottom = Math.max(dragStart.y, dragCurrent.y) - - const MIN_SELECTION_SIZE = 5 - const isValidSelection = - Math.abs(right - left) > MIN_SELECTION_SIZE && Math.abs(bottom - top) > MIN_SELECTION_SIZE - - if (isValidSelection) { - messageElements.forEach((element, messageId) => { - try { - const rect = element.getBoundingClientRect() - const container = scrollContainerRef.current! - - const elementTop = rect.top - container.getBoundingClientRect().top + container.scrollTop - const elementLeft = rect.left - container.getBoundingClientRect().left + container.scrollLeft - const elementBottom = elementTop + rect.height - const elementRight = elementLeft + rect.width - - const isIntersecting = !( - elementRight < left || - elementLeft > right || - elementBottom < top || - elementTop > bottom - ) - - if (isIntersecting) { - handleSelectMessage(messageId, true) - element.classList.add('selection-highlight') - setTimeout(() => element.classList.remove('selection-highlight'), 300) - } - } catch (error) { - console.error('Error calculating element intersection:', error) - } - }) - } setIsDragging(false) document.body.classList.remove('no-select') } const container = scrollContainerRef.current! - if (container) { - container.addEventListener('mousedown', handleMouseDown) - window.addEventListener('mousemove', handleMouseMove) - window.addEventListener('mouseup', handleMouseUp) - } + container?.addEventListener('mousedown', handleMouseDown) + window.addEventListener('mousemove', handleMouseMove) + window.addEventListener('mouseup', handleMouseUp) return () => { - if (container) { - container.removeEventListener('mousedown', handleMouseDown) - window.removeEventListener('mousemove', handleMouseMove) - window.removeEventListener('mouseup', handleMouseUp) - document.body.classList.remove('no-select') - } + container?.removeEventListener('mousedown', handleMouseDown) + window.removeEventListener('mousemove', handleMouseMove) + window.removeEventListener('mouseup', handleMouseUp) + document.body.classList.remove('no-select') } - }, [isMultiSelectMode, isDragging, dragStart, dragCurrent, handleSelectMessage, scrollContainerRef, messageElements]) + }, [isMultiSelectMode, isDragging, dragStart, scrollContainerRef, messageElements, handleSelectMessage]) if (!isDragging || !isMultiSelectMode) return null