mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-23 10:00:08 +08:00
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:
parent
42f5485899
commit
f6a496d1b9
@ -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 (
|
||||||
|
<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 (
|
return (
|
||||||
<ModelsWrapper>
|
<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)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user