refactor: clean up code for MessageGroupModelList (#6084)

* refactor: clean up comments and useless z-index

* refactor: remove useless styles, restore segment animation, extract renderLabel

* refactor: add left margin to the toggle button

* revert: font size
This commit is contained in:
one 2025-05-18 10:05:19 +08:00 committed by GitHub
parent 42f5485899
commit f6a496d1b9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -1,5 +1,6 @@
import { ArrowsAltOutlined, ShrinkOutlined } from '@ant-design/icons' import { ArrowsAltOutlined, ShrinkOutlined } from '@ant-design/icons'
import ModelAvatar from '@renderer/components/Avatar/ModelAvatar' import ModelAvatar from '@renderer/components/Avatar/ModelAvatar'
import { HStack } from '@renderer/components/Layout'
import Scrollbar from '@renderer/components/Scrollbar' import Scrollbar from '@renderer/components/Scrollbar'
import { useSettings } from '@renderer/hooks/useSettings' import { useSettings } from '@renderer/hooks/useSettings'
import { useAppDispatch } from '@renderer/store' import { useAppDispatch } from '@renderer/store'
@ -7,7 +8,7 @@ import { setFoldDisplayMode } from '@renderer/store/settings'
import type { Model } from '@renderer/types' import type { Model } from '@renderer/types'
import type { Message } from '@renderer/types/newMessage' import type { Message } from '@renderer/types/newMessage'
import { Avatar, Segmented as AntdSegmented, Tooltip } from 'antd' import { Avatar, Segmented as AntdSegmented, Tooltip } from 'antd'
import { FC } from 'react' import { FC, memo, useCallback } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import styled from 'styled-components' import styled from 'styled-components'
@ -25,39 +26,54 @@ const MessageGroupModelList: FC<MessageGroupModelListProps> = ({ messages, selec
const { foldDisplayMode } = useSettings() const { foldDisplayMode } = useSettings()
const isCompact = foldDisplayMode === 'compact' const isCompact = foldDisplayMode === 'compact'
const renderLabel = useCallback(
(message: Message) => {
const modelTip = message.model?.name
if (isCompact) {
return ( return (
<ModelsWrapper> <Tooltip key={message.id} title={modelTip} mouseEnterDelay={0.5}>
<AvatarWrapper
className="avatar-wrapper"
$isSelected={message.id === selectMessageId}
onClick={() => {
setSelectedMessage(message)
}}>
<ModelAvatar model={message.model as Model} size={22} />
</AvatarWrapper>
</Tooltip>
)
}
return (
<SegmentedLabel>
<ModelAvatar model={message.model as Model} size={20} />
<ModelName>{message.model?.name}</ModelName>
</SegmentedLabel>
)
},
[isCompact, selectMessageId, setSelectedMessage]
)
return (
<Container>
<DisplayModeToggle <DisplayModeToggle
displayMode={foldDisplayMode} displayMode={foldDisplayMode}
onClick={() => dispatch(setFoldDisplayMode(isCompact ? 'expanded' : 'compact'))}> onClick={() => dispatch(setFoldDisplayMode(isCompact ? 'expanded' : 'compact'))}>
<Tooltip <Tooltip
title={ title={
foldDisplayMode === 'compact' isCompact
? t(`message.message.multi_model_style.fold.expand`) ? t(`message.message.multi_model_style.fold.expand`)
: t('message.message.multi_model_style.fold.compress') : t('message.message.multi_model_style.fold.compress')
} }
placement="top"> placement="top">
{foldDisplayMode === 'compact' ? <ArrowsAltOutlined /> : <ShrinkOutlined />} {isCompact ? <ArrowsAltOutlined /> : <ShrinkOutlined />}
</Tooltip> </Tooltip>
</DisplayModeToggle> </DisplayModeToggle>
<ModelsContainer $displayMode={foldDisplayMode}> <ModelsContainer $displayMode={foldDisplayMode}>
{foldDisplayMode === 'compact' ? ( {isCompact ? (
/* Compact style display */ /* Compact style display */
<Avatar.Group className="avatar-group"> <Avatar.Group className="avatar-group">{messages.map((message) => renderLabel(message))}</Avatar.Group>
{messages.map((message, index) => (
<Tooltip key={index} title={message.model?.name} placement="top" mouseEnterDelay={0.2}>
<AvatarWrapper
className="avatar-wrapper"
isSelected={message.id === selectMessageId}
onClick={() => {
setSelectedMessage(message)
}}>
<ModelAvatar model={message.model as Model} size={28} />
</AvatarWrapper>
</Tooltip>
))}
</Avatar.Group>
) : ( ) : (
/* Expanded style display */ /* Expanded style display */
<Segmented <Segmented
@ -67,45 +83,32 @@ const MessageGroupModelList: FC<MessageGroupModelListProps> = ({ messages, selec
setSelectedMessage(message) setSelectedMessage(message)
}} }}
options={messages.map((message) => ({ options={messages.map((message) => ({
label: ( label: renderLabel(message),
<SegmentedLabel>
<ModelAvatar model={message.model as Model} size={20} />
<ModelName>{message.model?.name}</ModelName>
</SegmentedLabel>
),
value: message.id value: message.id
}))} }))}
size="small" size="small"
/> />
)} )}
</ModelsContainer> </ModelsContainer>
</ModelsWrapper> </Container>
) )
} }
const ModelsWrapper = styled.div` const Container = styled(HStack)`
position: relative;
display: flex;
flex: 1; flex: 1;
overflow: hidden; overflow: hidden;
align-items: center;
margin-left: 4px;
` `
const DisplayModeToggle = styled.div<{ displayMode: DisplayMode }>` const DisplayModeToggle = styled.div<{ displayMode: DisplayMode }>`
position: absolute;
left: 4px; /* Add more space on the left */
top: 50%;
transform: translateY(-50%);
z-index: 5;
width: 28px; /* Increase width */
height: 28px; /* Add height */
display: flex; display: flex;
justify-content: center;
align-items: center;
cursor: pointer; cursor: pointer;
padding: 2px 6px 3px 6px;
border-radius: 4px; border-radius: 4px;
padding: 2px; width: 26px;
height: 26px;
/* Add hover effect */
&:hover { &:hover {
background-color: var(--color-hover); background-color: var(--color-hover);
} }
@ -119,9 +122,7 @@ const ModelsContainer = styled(Scrollbar)<{ $displayMode: DisplayMode }>`
overflow-x: auto; overflow-x: auto;
flex: 1; flex: 1;
padding: 0 8px; padding: 0 8px;
margin-left: 24px; /* Space for toggle button */
/* Hide scrollbar to match original code */
&::-webkit-scrollbar { &::-webkit-scrollbar {
display: none; display: none;
} }
@ -131,27 +132,23 @@ const ModelsContainer = styled(Scrollbar)<{ $displayMode: DisplayMode }>`
display: flex; display: flex;
align-items: center; align-items: center;
flex-wrap: nowrap; flex-wrap: nowrap;
position: relative;
padding: 6px 4px; padding: 6px 4px;
/* Base style - default overlapping effect */ /* Base style - default overlapping effect */
& > * { & > * {
margin-left: -6px !important; margin-left: -6px !important;
/* Separate transition properties to avoid conflicts */
transition: transition:
transform 0.18s ease-out, transform 0.18s ease-out,
margin 0.18s ease-out !important; margin 0.18s ease-out !important;
position: relative; position: relative;
/* Only use will-change for transform to reduce rendering overhead */
will-change: transform; will-change: transform;
} }
/* First element has no left margin */
& > *:first-child { & > *:first-child {
margin-left: 0 !important; margin-left: 0 !important;
} }
/* Using :has() selector to handle the element before the hovered one */ /* Element before the hovered one */
& > *:has(+ *:hover) { & > *:has(+ *:hover) {
margin-right: 2px !important; margin-right: 2px !important;
/* Use transform instead of margin to reduce layout recalculations */ /* Use transform instead of margin to reduce layout recalculations */
@ -171,52 +168,24 @@ const ModelsContainer = styled(Scrollbar)<{ $displayMode: DisplayMode }>`
} }
` `
const AvatarWrapper = styled.div<{ isSelected: boolean }>` const AvatarWrapper = styled.div<{ $isSelected: boolean }>`
cursor: pointer; cursor: pointer;
display: inline-flex; display: inline-flex;
border-radius: 50%; border-radius: 50%;
/* Keep z-index separate from transitions to avoid rendering issues */
z-index: ${(props) => (props.isSelected ? 2 : 0)};
background: var(--color-background); background: var(--color-background);
/* Simplify transitions to reduce jittering */
transition: transition:
transform 0.18s ease-out, transform 0.18s ease-out,
margin 0.18s ease-out, margin 0.18s ease-out,
box-shadow 0.18s ease-out,
filter 0.18s ease-out; filter 0.18s ease-out;
box-shadow: 0 0 0 1px var(--color-background); z-index: ${(props) => (props.$isSelected ? 1 : 0)};
border: ${(props) => (props.$isSelected ? '2px solid var(--color-primary)' : 'none')};
/* Use CSS variables to define animation parameters for easy adjustment */
--hover-scale: 1.15;
--hover-x-offset: 6px;
--hover-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
&:hover { &:hover {
/* z-index is applied immediately, not part of the transition */ transform: translateX(6px) scale(1.15);
z-index: 10;
transform: translateX(var(--hover-x-offset)) scale(var(--hover-scale));
box-shadow: var(--hover-shadow);
filter: brightness(1.02); filter: brightness(1.02);
margin-left: 8px !important; margin-left: 8px !important;
margin-right: 4px !important; margin-right: 4px !important;
} }
${(props) =>
props.isSelected &&
`
border: 2px solid var(--color-primary);
z-index: 2;
&:hover {
/* z-index is applied immediately, not part of the transition */
z-index: 10;
border: 2px solid var(--color-primary);
filter: brightness(1.02);
transform: translateX(var(--hover-x-offset)) scale(var(--hover-scale));
margin-left: 8px !important;
margin-right: 4px !important;
}
`}
` `
const Segmented = styled(AntdSegmented)` const Segmented = styled(AntdSegmented)`
@ -224,21 +193,15 @@ const Segmented = styled(AntdSegmented)`
background-color: transparent !important; background-color: transparent !important;
.ant-segmented-item { .ant-segmented-item {
background-color: transparent !important;
transition: none !important;
border-radius: var(--list-item-border-radius) !important; border-radius: var(--list-item-border-radius) !important;
box-shadow: none !important;
&:hover { &:hover {
background: transparent !important; background: transparent !important;
} }
} }
.ant-segmented-thumb, .ant-segmented-thumb,
.ant-segmented-item-selected { .ant-segmented-item-selected {
background-color: transparent !important;
border: 0.5px solid var(--color-border); border: 0.5px solid var(--color-border);
transition: none !important;
border-radius: var(--list-item-border-radius) !important; border-radius: var(--list-item-border-radius) !important;
box-shadow: none !important;
} }
` `
@ -254,4 +217,4 @@ const ModelName = styled.span`
font-size: 12px; font-size: 12px;
` `
export default MessageGroupModelList export default memo(MessageGroupModelList)