From 0113447481fc6050014d830094006b60ef457478 Mon Sep 17 00:00:00 2001 From: Konv Suu <2583695112@qq.com> Date: Thu, 31 Jul 2025 21:06:22 +0800 Subject: [PATCH] feat: add multi-select mode wrapper for message component (#8653) * feat: add multi-select mode wrapper for message component * fix: update * update * update * chore: minor updates * fix: add drag threshold --- .../src/pages/home/Messages/Message.tsx | 130 +++++++++++------- .../src/pages/home/Messages/SelectionBox.tsx | 36 +++-- 2 files changed, 108 insertions(+), 58 deletions(-) diff --git a/src/renderer/src/pages/home/Messages/Message.tsx b/src/renderer/src/pages/home/Messages/Message.tsx index a2e6633f77..0c20f0d842 100644 --- a/src/renderer/src/pages/home/Messages/Message.tsx +++ b/src/renderer/src/pages/home/Messages/Message.tsx @@ -2,6 +2,7 @@ import { loggerService } from '@logger' import Scrollbar from '@renderer/components/Scrollbar' import { useMessageEditing } from '@renderer/context/MessageEditingContext' import { useAssistant } from '@renderer/hooks/useAssistant' +import { useChatContext } from '@renderer/hooks/useChatContext' import { useMessageOperations } from '@renderer/hooks/useMessageOperations' import { useModel } from '@renderer/hooks/useModel' import { useSettings } from '@renderer/hooks/useSettings' @@ -38,6 +39,16 @@ interface Props { const logger = loggerService.withContext('MessageItem') +const WrapperContainer = ({ + isMultiSelectMode, + children +}: { + isMultiSelectMode: boolean + children: React.ReactNode +}) => { + return isMultiSelectMode ? : children +} + const MessageItem: FC = ({ message, topic, @@ -49,6 +60,7 @@ const MessageItem: FC = ({ }) => { const { t } = useTranslation() const { assistant, setModel } = useAssistant(message.assistantId) + const { isMultiSelectMode } = useChatContext(topic) const model = useModel(getMessageModelId(message), message.model?.provider) || message.model const { messageFont, fontSize, messageStyle } = useSettings() const { editMessageBlocks, resendUserMessageWithEdit, editMessage } = useMessageOperations(topic) @@ -122,7 +134,15 @@ const MessageItem: FC = ({ if (message.type === 'clear') { return ( - EventEmitter.emit(EVENT_NAMES.NEW_CONTEXT)}> + { + if (isMultiSelectMode) { + return + } + EventEmitter.emit(EVENT_NAMES.NEW_CONTEXT) + }}> {t('chat.message.new.context')} @@ -131,56 +151,64 @@ const MessageItem: FC = ({ } return ( - - - {isEditing && ( - + + - )} - {!isEditing && ( - <> - - - - - - {showMenubar && ( - - } - setModel={setModel} - /> - - )} - - )} - + {isEditing && ( + + )} + {!isEditing && ( + <> + + + + + + {showMenubar && ( + + } + setModel={setModel} + /> + + )} + + )} + + ) } @@ -232,9 +260,11 @@ const MessageFooter = styled.div<{ $isLastMessage: boolean; $messageStyle: 'plai margin-top: 8px; ` -const NewContextMessage = styled.div` +const NewContextMessage = styled.div<{ isMultiSelectMode: boolean }>` cursor: pointer; flex: 1; + + ${({ isMultiSelectMode }) => isMultiSelectMode && 'cursor: default;'} ` export default memo(MessageItem) diff --git a/src/renderer/src/pages/home/Messages/SelectionBox.tsx b/src/renderer/src/pages/home/Messages/SelectionBox.tsx index ab48b69a8e..2d12f0305c 100644 --- a/src/renderer/src/pages/home/Messages/SelectionBox.tsx +++ b/src/renderer/src/pages/home/Messages/SelectionBox.tsx @@ -17,9 +17,14 @@ const SelectionBox: React.FC = ({ const [isDragging, setIsDragging] = useState(false) const [dragStart, setDragStart] = useState({ x: 0, y: 0 }) const [dragCurrent, setDragCurrent] = useState({ x: 0, y: 0 }) + const [isMouseDown, setIsMouseDown] = useState(false) const dragSelectedIds = useRef>(new Set()) + // 拖拽阈值,只有移动距离超过这个值才开始框选 + // 避免触控板点击触发拖拽 + const DRAG_THRESHOLD = 5 + useEffect(() => { if (!isMultiSelectMode) return @@ -39,20 +44,30 @@ const SelectionBox: React.FC = ({ e.preventDefault() - setIsDragging(true) + setIsMouseDown(true) const pos = updateDragPos(e) setDragStart(pos) setDragCurrent(pos) dragSelectedIds.current.clear() - document.body.classList.add('no-select') } const handleMouseMove = (e: MouseEvent) => { + if (!isMouseDown) return + + const pos = updateDragPos(e) + + const deltaX = Math.abs(pos.x - dragStart.x) + const deltaY = Math.abs(pos.y - dragStart.y) + const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY) + + if (!isDragging && distance > DRAG_THRESHOLD) { + setIsDragging(true) + document.body.classList.add('no-select') + } + if (!isDragging) return e.preventDefault() - - const pos = updateDragPos(e) setDragCurrent(pos) // 计算当前框选矩形 @@ -69,6 +84,9 @@ const SelectionBox: React.FC = ({ const checkbox = el.querySelector('input[type="checkbox"]') as HTMLInputElement | null const isAlreadySelected = checkbox?.checked || false + // 清除上下文这类消息也会被选中,所以需要跳过 + if (!checkbox) return + // 如果已经被记录为拖动选中,跳过 if (dragSelectedIds.current.has(id)) return @@ -94,9 +112,11 @@ const SelectionBox: React.FC = ({ } const handleMouseUp = () => { - if (!isDragging) return - setIsDragging(false) - document.body.classList.remove('no-select') + setIsMouseDown(false) + if (isDragging) { + setIsDragging(false) + document.body.classList.remove('no-select') + } } const container = scrollContainerRef.current! @@ -110,7 +130,7 @@ const SelectionBox: React.FC = ({ window.removeEventListener('mouseup', handleMouseUp) document.body.classList.remove('no-select') } - }, [isMultiSelectMode, isDragging, dragStart, scrollContainerRef, messageElements, handleSelectMessage]) + }, [isMultiSelectMode, isDragging, isMouseDown, dragStart, scrollContainerRef, messageElements, handleSelectMessage]) if (!isDragging || !isMultiSelectMode) return null