fix: fix fold selected (#4058)

* fix: 修复foldSelected问题

* refactor: 优化布局定位
This commit is contained in:
Teo 2025-03-28 21:22:45 +08:00 committed by GitHub
parent f1a03916e7
commit 21f1b8b373
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 63 additions and 49 deletions

View File

@ -56,6 +56,8 @@ const Container = styled.div`
const Main = styled(Flex)` const Main = styled(Flex)`
height: calc(100vh - var(--navbar-height)); height: calc(100vh - var(--navbar-height));
// 设置为containing block方便子元素fixed定位
transform: translateZ(0);
` `
export default Chat export default Chat

View File

@ -24,7 +24,6 @@ const ChatNavigation: FC<ChatNavigationProps> = ({ containerId }) => {
const lastMoveTime = useRef(0) const lastMoveTime = useRef(0)
const { topicPosition, showTopics } = useSettings() const { topicPosition, showTopics } = useSettings()
const showRightTopics = topicPosition === 'right' && showTopics const showRightTopics = topicPosition === 'right' && showTopics
const right = showRightTopics ? 'calc(var(--topic-list-width) + 16px)' : '16px'
// Reset hide timer and make buttons visible // Reset hide timer and make buttons visible
const resetHideTimer = useCallback(() => { const resetHideTimer = useCallback(() => {
@ -263,24 +262,11 @@ const ChatNavigation: FC<ChatNavigationProps> = ({ containerId }) => {
clearTimeout(hideTimer) clearTimeout(hideTimer)
} }
} }
}, [ }, [containerId, hideTimer, resetHideTimer, isNearButtons, handleMouseEnter, handleMouseLeave, showRightTopics])
containerId,
hideTimer,
resetHideTimer,
isNearButtons,
handleMouseEnter,
handleMouseLeave,
right,
showRightTopics
])
return ( return (
<> <>
<NavigationContainer <NavigationContainer $isVisible={isVisible} onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave}>
$isVisible={isVisible}
$right={right}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}>
<ButtonGroup> <ButtonGroup>
<Tooltip title={t('chat.navigation.prev')} placement="left"> <Tooltip title={t('chat.navigation.prev')} placement="left">
<NavigationButton <NavigationButton
@ -332,12 +318,11 @@ const ChatNavigation: FC<ChatNavigationProps> = ({ containerId }) => {
interface NavigationContainerProps { interface NavigationContainerProps {
$isVisible: boolean $isVisible: boolean
$right: string
} }
const NavigationContainer = styled.div<NavigationContainerProps>` const NavigationContainer = styled.div<NavigationContainerProps>`
position: fixed; position: fixed;
right: ${(props) => props.$right}; right: 16px;
top: 50%; top: 50%;
transform: translateY(-50%) translateX(${(props) => (props.$isVisible ? 0 : '100%')}); transform: translateY(-50%) translateX(${(props) => (props.$isVisible ? 0 : '100%')});
z-index: 999; z-index: 999;

View File

@ -33,9 +33,6 @@ const MessageAnchorLine: FC<MessageLineProps> = ({ messages }) => {
const messageItemsRef = useRef<Map<string, HTMLDivElement>>(new Map()) const messageItemsRef = useRef<Map<string, HTMLDivElement>>(new Map())
const containerRef = useRef<HTMLDivElement>(null) const containerRef = useRef<HTMLDivElement>(null)
const [mouseY, setMouseY] = useState<number | null>(null) const [mouseY, setMouseY] = useState<number | null>(null)
const { topicPosition, showTopics } = useSettings()
const showRightTopics = topicPosition === 'right' && showTopics
const right = showRightTopics ? 'calc(var(--topic-list-width) + 15px)' : '15px'
const [listOffsetY, setListOffsetY] = useState(0) const [listOffsetY, setListOffsetY] = useState(0)
const [containerHeight, setContainerHeight] = useState<number | null>(null) const [containerHeight, setContainerHeight] = useState<number | null>(null)
@ -160,7 +157,6 @@ const MessageAnchorLine: FC<MessageLineProps> = ({ messages }) => {
ref={containerRef} ref={containerRef}
onMouseMove={handleMouseMove} onMouseMove={handleMouseMove}
onMouseLeave={handleMouseLeave} onMouseLeave={handleMouseLeave}
$right={right}
$height={containerHeight}> $height={containerHeight}>
<MessagesList ref={messagesListRef} style={{ transform: `translateY(${listOffsetY}px)` }}> <MessagesList ref={messagesListRef} style={{ transform: `translateY(${listOffsetY}px)` }}>
{messages.map((message, index) => { {messages.map((message, index) => {
@ -226,11 +222,11 @@ const MessageItemContainer = styled.div`
transform-origin: right center; transform-origin: right center;
` `
const MessageLineContainer = styled.div<{ $right: string; $height: number | null }>` const MessageLineContainer = styled.div<{ $height: number | null }>`
width: 14px; width: 14px;
position: fixed; position: fixed;
top: ${(props) => (props.$height ? `calc(${props.$height / 2}px + var(--status-bar-height))` : '50%')}; top: ${(props) => (props.$height ? `calc(${props.$height / 2}px + var(--status-bar-height))` : '50%')};
right: ${(props) => props.$right}; right: 13px;
max-height: ${(props) => (props.$height ? `${props.$height}px` : 'calc(100% - var(--status-bar-height) * 2)')}; max-height: ${(props) => (props.$height ? `${props.$height}px` : 'calc(100% - var(--status-bar-height) * 2)')};
transform: translateY(-50%); transform: translateY(-50%);
z-index: 0; z-index: 0;

View File

@ -6,7 +6,7 @@ import { MultiModelMessageStyle } from '@renderer/store/settings'
import type { Message, Topic } from '@renderer/types' import type { Message, Topic } from '@renderer/types'
import { classNames } from '@renderer/utils' import { classNames } from '@renderer/utils'
import { Popover } from 'antd' import { Popover } from 'antd'
import { memo, useCallback, useEffect, useState } from 'react' import { memo, useCallback, useEffect, useRef, useState } from 'react'
import styled, { css } from 'styled-components' import styled, { css } from 'styled-components'
import MessageGroupMenuBar from './MessageGroupMenuBar' import MessageGroupMenuBar from './MessageGroupMenuBar'
@ -27,14 +27,53 @@ const MessageGroup = ({ messages, topic, hidePresetMessages }: Props) => {
) )
const messageLength = messages.length const messageLength = messages.length
const prevMessageLengthRef = useRef(messageLength)
const [selectedIndex, setSelectedIndex] = useState(messageLength - 1) const [selectedIndex, setSelectedIndex] = useState(messageLength - 1)
const getSelectedMessageId = useCallback(() => {
const selectedMessage = messages.find((message) => message.foldSelected)
if (selectedMessage) {
return selectedMessage.id
}
return messages[0]?.id
}, [messages])
const setSelectedMessage = useCallback(
(message: Message) => {
messages.forEach(async (m) => {
await editMessage(m.id, { foldSelected: m.id === message.id })
})
setTimeout(() => {
const messageElement = document.getElementById(`message-${message.id}`)
if (messageElement) {
messageElement.scrollIntoView({ behavior: 'smooth', block: 'start' })
}
}, 200)
},
[editMessage, messages]
)
const isGrouped = messageLength > 1 && messages.every((m) => m.role === 'assistant') const isGrouped = messageLength > 1 && messages.every((m) => m.role === 'assistant')
const isHorizontal = multiModelMessageStyle === 'horizontal' const isHorizontal = multiModelMessageStyle === 'horizontal'
const isGrid = multiModelMessageStyle === 'grid' const isGrid = multiModelMessageStyle === 'grid'
useEffect(() => { useEffect(() => {
setSelectedIndex(messageLength - 1) if (messageLength > prevMessageLengthRef.current) {
setSelectedIndex(messageLength - 1)
const lastMessage = messages[messageLength - 1]
if (lastMessage) {
setSelectedMessage(lastMessage)
}
} else {
const selectedId = getSelectedMessageId()
const newIndex = messages.findIndex((msg) => msg.id === selectedId)
if (newIndex !== -1) {
setSelectedIndex(newIndex)
}
}
prevMessageLengthRef.current = messageLength
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [messageLength]) }, [messageLength])
// 添加对流程图节点点击事件的监听 // 添加对流程图节点点击事件的监听
@ -70,22 +109,6 @@ const MessageGroup = ({ messages, topic, hidePresetMessages }: Props) => {
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [messages, selectedIndex, isGrouped, messageLength]) }, [messages, selectedIndex, isGrouped, messageLength])
const setSelectedMessage = useCallback(
(message: Message) => {
messages.forEach(async (m) => {
await editMessage(m.id, { foldSelected: m.id === message.id })
})
setTimeout(() => {
const messageElement = document.getElementById(`message-${message.id}`)
if (messageElement) {
messageElement.scrollIntoView({ behavior: 'smooth', block: 'start' })
}
}, 200)
},
[editMessage, messages]
)
// 添加对LOCATE_MESSAGE事件的监听 // 添加对LOCATE_MESSAGE事件的监听
useEffect(() => { useEffect(() => {
// 为每个消息注册一个定位事件监听器 // 为每个消息注册一个定位事件监听器
@ -146,7 +169,7 @@ const MessageGroup = ({ messages, topic, hidePresetMessages }: Props) => {
className={classNames({ className={classNames({
'group-message-wrapper': message.role === 'assistant' && isHorizontal && isGrouped, 'group-message-wrapper': message.role === 'assistant' && isHorizontal && isGrouped,
[multiModelMessageStyle]: isGrouped, [multiModelMessageStyle]: isGrouped,
selected: 'foldSelected' in message ? message.foldSelected : index === 0 selected: message.id === getSelectedMessageId()
})}> })}>
<MessageStream {...messageProps} /> <MessageStream {...messageProps} />
</MessageWrapper> </MessageWrapper>
@ -183,7 +206,8 @@ const MessageGroup = ({ messages, topic, hidePresetMessages }: Props) => {
selectedIndex, selectedIndex,
topic, topic,
hidePresetMessages, hidePresetMessages,
gridPopoverTrigger gridPopoverTrigger,
getSelectedMessageId
] ]
) )
@ -210,6 +234,7 @@ const MessageGroup = ({ messages, topic, hidePresetMessages }: Props) => {
}) })
}} }}
messages={messages} messages={messages}
selectMessageId={getSelectedMessageId()}
setSelectedMessage={setSelectedMessage} setSelectedMessage={setSelectedMessage}
topic={topic} topic={topic}
/> />

View File

@ -21,6 +21,7 @@ interface Props {
multiModelMessageStyle: MultiModelMessageStyle multiModelMessageStyle: MultiModelMessageStyle
setMultiModelMessageStyle: (style: MultiModelMessageStyle) => void setMultiModelMessageStyle: (style: MultiModelMessageStyle) => void
messages: Message[] messages: Message[]
selectMessageId: string
setSelectedMessage: (message: Message) => void setSelectedMessage: (message: Message) => void
topic: Topic topic: Topic
} }
@ -29,6 +30,7 @@ const MessageGroupMenuBar: FC<Props> = ({
multiModelMessageStyle, multiModelMessageStyle,
setMultiModelMessageStyle, setMultiModelMessageStyle,
messages, messages,
selectMessageId,
setSelectedMessage, setSelectedMessage,
topic topic
}) => { }) => {
@ -75,7 +77,11 @@ const MessageGroupMenuBar: FC<Props> = ({
))} ))}
</LayoutContainer> </LayoutContainer>
{multiModelMessageStyle === 'fold' && ( {multiModelMessageStyle === 'fold' && (
<MessageGroupModelList messages={messages} setSelectedMessage={setSelectedMessage} /> <MessageGroupModelList
messages={messages}
selectMessageId={selectMessageId}
setSelectedMessage={setSelectedMessage}
/>
)} )}
{multiModelMessageStyle === 'grid' && <MessageGroupSettings />} {multiModelMessageStyle === 'grid' && <MessageGroupSettings />}
</HStack> </HStack>

View File

@ -12,12 +12,13 @@ import styled from 'styled-components'
interface MessageGroupModelListProps { interface MessageGroupModelListProps {
messages: Message[] messages: Message[]
selectMessageId: string
setSelectedMessage: (message: Message) => void setSelectedMessage: (message: Message) => void
} }
type DisplayMode = 'compact' | 'expanded' type DisplayMode = 'compact' | 'expanded'
const MessageGroupModelList: FC<MessageGroupModelListProps> = ({ messages, setSelectedMessage }) => { const MessageGroupModelList: FC<MessageGroupModelListProps> = ({ messages, selectMessageId, setSelectedMessage }) => {
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
const { t } = useTranslation() const { t } = useTranslation()
const { foldDisplayMode } = useSettings() const { foldDisplayMode } = useSettings()
@ -47,7 +48,7 @@ const MessageGroupModelList: FC<MessageGroupModelListProps> = ({ messages, setSe
<Tooltip key={index} title={message.model?.name} placement="top" mouseEnterDelay={0.2}> <Tooltip key={index} title={message.model?.name} placement="top" mouseEnterDelay={0.2}>
<AvatarWrapper <AvatarWrapper
className="avatar-wrapper" className="avatar-wrapper"
isSelected={'foldSelected' in message ? message.foldSelected! : index === 0} isSelected={message.id === selectMessageId}
onClick={() => { onClick={() => {
setSelectedMessage(message) setSelectedMessage(message)
}}> }}>
@ -59,7 +60,7 @@ const MessageGroupModelList: FC<MessageGroupModelListProps> = ({ messages, setSe
) : ( ) : (
/* Expanded style display */ /* Expanded style display */
<Segmented <Segmented
value={messages.find((message) => message.foldSelected)?.id || messages[0].id} value={selectMessageId}
onChange={(value) => { onChange={(value) => {
const message = messages.find((message) => message.id === value) as Message const message = messages.find((message) => message.id === value) as Message
setSelectedMessage(message) setSelectedMessage(message)

View File

@ -278,7 +278,6 @@ export const sendMessage =
const assistantMessage = getAssistantMessage({ assistant, topic }) const assistantMessage = getAssistantMessage({ assistant, topic })
assistantMessage.askId = userMessage.id assistantMessage.askId = userMessage.id
assistantMessage.status = 'sending' assistantMessage.status = 'sending'
assistantMessage.foldSelected = true
assistantMessages.push(assistantMessage) assistantMessages.push(assistantMessage)
} }