refactor: Extract message group menu bar into a separate component
This commit is contained in:
parent
bc77c423b3
commit
0c13e708b9
@ -1,24 +1,14 @@
|
||||
import {
|
||||
ColumnHeightOutlined,
|
||||
ColumnWidthOutlined,
|
||||
DeleteOutlined,
|
||||
FolderOutlined,
|
||||
NumberOutlined
|
||||
} from '@ant-design/icons'
|
||||
import ModelAvatar from '@renderer/components/Avatar/ModelAvatar'
|
||||
import { HStack } from '@renderer/components/Layout'
|
||||
import Scrollbar from '@renderer/components/Scrollbar'
|
||||
import { useSettings } from '@renderer/hooks/useSettings'
|
||||
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
|
||||
import { MultiModelMessageStyle } from '@renderer/store/settings'
|
||||
import { Message, Model, Topic } from '@renderer/types'
|
||||
import { Button, Popover, Segmented as AntdSegmented } from 'antd'
|
||||
import { Dispatch, FC, SetStateAction, useEffect, useState } from 'react'
|
||||
import { Message, Topic } from '@renderer/types'
|
||||
import { Popover } from 'antd'
|
||||
import { Dispatch, FC, memo, SetStateAction, useCallback, useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled, { css } from 'styled-components'
|
||||
|
||||
import MessageItem from './Message'
|
||||
import MessageGroupSettings from './MessageGroupSettings'
|
||||
import MessageGroupMenuBar from './MessageGroupMenuBar'
|
||||
|
||||
interface Props {
|
||||
messages: (Message & { index: number })[]
|
||||
@ -39,7 +29,7 @@ const MessageGroup: FC<Props> = ({
|
||||
onGetMessages,
|
||||
onDeleteGroupMessages
|
||||
}) => {
|
||||
const { multiModelMessageStyle: multiModelMessageStyleSetting, gridColumns } = useSettings()
|
||||
const { multiModelMessageStyle: multiModelMessageStyleSetting, gridColumns, gridPopoverTrigger } = useSettings()
|
||||
const { t } = useTranslation()
|
||||
|
||||
const [multiModelMessageStyle, setMultiModelMessageStyle] =
|
||||
@ -48,11 +38,10 @@ const MessageGroup: FC<Props> = ({
|
||||
const messageLength = messages.length
|
||||
const [selectedIndex, setSelectedIndex] = useState(messageLength - 1)
|
||||
|
||||
const { gridPopoverTrigger } = useSettings()
|
||||
|
||||
const isGrouped = messageLength > 1
|
||||
const isHorizontal = multiModelMessageStyle === 'horizontal'
|
||||
|
||||
const onDelete = async () => {
|
||||
const onDelete = useCallback(async () => {
|
||||
window.modal.confirm({
|
||||
title: t('message.group.delete.title'),
|
||||
content: t('message.group.delete.content'),
|
||||
@ -66,26 +55,50 @@ const MessageGroup: FC<Props> = ({
|
||||
askId && onDeleteGroupMessages?.(askId)
|
||||
}
|
||||
})
|
||||
}
|
||||
}, [messages, onDeleteGroupMessages, t])
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedIndex(messageLength - 1)
|
||||
}, [messageLength])
|
||||
|
||||
const isHorizontal = multiModelMessageStyle === 'horizontal'
|
||||
|
||||
return (
|
||||
<GroupContainer $isGrouped={isGrouped} $layout={multiModelMessageStyle}>
|
||||
<GridContainer $count={messageLength} $layout={multiModelMessageStyle} $gridColumns={gridColumns}>
|
||||
{messages.map((message, index) =>
|
||||
multiModelMessageStyle === 'grid' && message.role === 'assistant' && isGrouped ? (
|
||||
<Popover
|
||||
content={
|
||||
{messages.map((message, index) => {
|
||||
const isGridGroupMessage = multiModelMessageStyle === 'grid' && message.role === 'assistant' && isGrouped
|
||||
if (isGridGroupMessage) {
|
||||
return (
|
||||
<Popover
|
||||
content={
|
||||
<MessageWrapper
|
||||
$layout={multiModelMessageStyle}
|
||||
$selected={index === selectedIndex}
|
||||
$isGrouped={isGrouped}
|
||||
$isInPopover={true}
|
||||
key={message.id}>
|
||||
<MessageItem
|
||||
isGrouped={isGrouped}
|
||||
message={message}
|
||||
topic={topic}
|
||||
index={message.index}
|
||||
hidePresetMessages={hidePresetMessages}
|
||||
style={{
|
||||
paddingTop: isGrouped && ['horizontal', 'grid'].includes(multiModelMessageStyle) ? 0 : 15
|
||||
}}
|
||||
onSetMessages={onSetMessages}
|
||||
onDeleteMessage={onDeleteMessage}
|
||||
onGetMessages={onGetMessages}
|
||||
/>
|
||||
</MessageWrapper>
|
||||
}
|
||||
trigger={gridPopoverTrigger}
|
||||
styles={{ root: { maxWidth: '60vw', minWidth: '550px', overflowY: 'auto', zIndex: 1000 } }}
|
||||
getPopupContainer={(triggerNode) => triggerNode.parentNode as HTMLElement}
|
||||
key={message.id}>
|
||||
<MessageWrapper
|
||||
$layout={multiModelMessageStyle}
|
||||
$selected={index === selectedIndex}
|
||||
$isGrouped={isGrouped}
|
||||
$isInPopover={true}
|
||||
key={message.id}>
|
||||
<MessageItem
|
||||
isGrouped={isGrouped}
|
||||
@ -93,46 +106,24 @@ const MessageGroup: FC<Props> = ({
|
||||
topic={topic}
|
||||
index={message.index}
|
||||
hidePresetMessages={hidePresetMessages}
|
||||
style={{
|
||||
paddingTop: isGrouped && ['horizontal', 'grid'].includes(multiModelMessageStyle) ? 0 : 15
|
||||
}}
|
||||
style={
|
||||
gridPopoverTrigger === 'hover' && isGrouped
|
||||
? {
|
||||
paddingTop: isGrouped && ['horizontal', 'grid'].includes(multiModelMessageStyle) ? 0 : 15,
|
||||
overflow: isGrouped ? 'hidden' : 'auto',
|
||||
maxHeight: isGrouped ? '280px' : 'unset'
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
onSetMessages={onSetMessages}
|
||||
onDeleteMessage={onDeleteMessage}
|
||||
onGetMessages={onGetMessages}
|
||||
/>
|
||||
</MessageWrapper>
|
||||
}
|
||||
trigger={gridPopoverTrigger}
|
||||
styles={{ root: { maxWidth: '60vw', minWidth: '550px', overflowY: 'auto', zIndex: 1000 } }}
|
||||
getPopupContainer={(triggerNode) => triggerNode.parentNode as HTMLElement}
|
||||
key={message.id}>
|
||||
<MessageWrapper
|
||||
$layout={multiModelMessageStyle}
|
||||
$selected={index === selectedIndex}
|
||||
$isGrouped={isGrouped}
|
||||
key={message.id}>
|
||||
<MessageItem
|
||||
isGrouped={isGrouped}
|
||||
message={message}
|
||||
topic={topic}
|
||||
index={message.index}
|
||||
hidePresetMessages={hidePresetMessages}
|
||||
style={
|
||||
gridPopoverTrigger === 'hover' && isGrouped
|
||||
? {
|
||||
paddingTop: isGrouped && ['horizontal', 'grid'].includes(multiModelMessageStyle) ? 0 : 15,
|
||||
overflow: isGrouped ? 'hidden' : 'auto',
|
||||
maxHeight: isGrouped ? '280px' : 'unset'
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
onSetMessages={onSetMessages}
|
||||
onDeleteMessage={onDeleteMessage}
|
||||
onGetMessages={onGetMessages}
|
||||
/>
|
||||
</MessageWrapper>
|
||||
</Popover>
|
||||
) : (
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<MessageWrapper
|
||||
$layout={multiModelMessageStyle}
|
||||
$selected={index === selectedIndex}
|
||||
@ -152,59 +143,17 @@ const MessageGroup: FC<Props> = ({
|
||||
/>
|
||||
</MessageWrapper>
|
||||
)
|
||||
)}
|
||||
})}
|
||||
</GridContainer>
|
||||
{isGrouped && (
|
||||
<GroupMenuBar className="group-menu-bar" $layout={multiModelMessageStyle}>
|
||||
<HStack style={{ alignItems: 'center', flex: 1, overflow: 'hidden' }}>
|
||||
<LayoutContainer>
|
||||
{['fold', 'vertical', 'horizontal', 'grid'].map((layout) => (
|
||||
<LayoutOption
|
||||
key={layout}
|
||||
active={multiModelMessageStyle === layout}
|
||||
onClick={() => setMultiModelMessageStyle(layout as MultiModelMessageStyle)}>
|
||||
{layout === 'fold' ? (
|
||||
<FolderOutlined />
|
||||
) : layout === 'horizontal' ? (
|
||||
<ColumnWidthOutlined />
|
||||
) : layout === 'vertical' ? (
|
||||
<ColumnHeightOutlined />
|
||||
) : (
|
||||
<NumberOutlined />
|
||||
)}
|
||||
</LayoutOption>
|
||||
))}
|
||||
</LayoutContainer>
|
||||
{multiModelMessageStyle === 'fold' && (
|
||||
<ModelsContainer>
|
||||
<Segmented
|
||||
value={selectedIndex.toString()}
|
||||
onChange={(value) => {
|
||||
setSelectedIndex(Number(value))
|
||||
EventEmitter.emit(EVENT_NAMES.LOCATE_MESSAGE + ':' + messages[Number(value)].id, false)
|
||||
}}
|
||||
options={messages.map((message, index) => ({
|
||||
label: (
|
||||
<SegmentedLabel>
|
||||
<ModelAvatar model={message.model as Model} size={20} />
|
||||
<ModelName>{message.model?.name}</ModelName>
|
||||
</SegmentedLabel>
|
||||
),
|
||||
value: index.toString()
|
||||
}))}
|
||||
size="small"
|
||||
/>
|
||||
</ModelsContainer>
|
||||
)}
|
||||
{multiModelMessageStyle === 'grid' && <MessageGroupSettings />}
|
||||
</HStack>
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
icon={<DeleteOutlined style={{ color: 'var(--color-error)' }} />}
|
||||
onClick={onDelete}
|
||||
/>
|
||||
</GroupMenuBar>
|
||||
<MessageGroupMenuBar
|
||||
multiModelMessageStyle={multiModelMessageStyle}
|
||||
setMultiModelMessageStyle={setMultiModelMessageStyle}
|
||||
messages={messages}
|
||||
selectedIndex={selectedIndex}
|
||||
setSelectedIndex={setSelectedIndex}
|
||||
onDelete={onDelete}
|
||||
/>
|
||||
)}
|
||||
</GroupContainer>
|
||||
)
|
||||
@ -288,74 +237,4 @@ const MessageWrapper = styled(Scrollbar)<MessageWrapperProps>`
|
||||
`}
|
||||
`
|
||||
|
||||
const GroupMenuBar = styled.div<{ $layout: MultiModelMessageStyle }>`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 6px 10px;
|
||||
border-radius: 6px;
|
||||
margin-top: 10px;
|
||||
justify-content: space-between;
|
||||
overflow: hidden;
|
||||
border: 0.5px solid var(--color-border);
|
||||
height: 40px;
|
||||
margin-left: ${({ $layout }) => (['horizontal', 'grid'].includes($layout) ? '0' : '40px')};
|
||||
transition: all 0.3s ease;
|
||||
`
|
||||
|
||||
const LayoutContainer = styled.div`
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-direction: row;
|
||||
`
|
||||
|
||||
const LayoutOption = styled.div<{ active: boolean }>`
|
||||
cursor: pointer;
|
||||
padding: 2px 10px;
|
||||
border-radius: 4px;
|
||||
background-color: ${({ active }) => (active ? 'var(--color-background-soft)' : 'transparent')};
|
||||
|
||||
&:hover {
|
||||
background-color: ${({ active }) => (active ? 'var(--color-background-soft)' : 'var(--color-hover)')};
|
||||
}
|
||||
`
|
||||
|
||||
const ModelsContainer = styled(Scrollbar)`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
`
|
||||
|
||||
const Segmented = styled(AntdSegmented)`
|
||||
.ant-segmented-item {
|
||||
background-color: transparent !important;
|
||||
transition: none !important;
|
||||
&:hover {
|
||||
background: transparent !important;
|
||||
}
|
||||
}
|
||||
.ant-segmented-thumb,
|
||||
.ant-segmented-item-selected {
|
||||
background-color: transparent !important;
|
||||
border: 0.5px solid var(--color-border);
|
||||
transition: none !important;
|
||||
}
|
||||
`
|
||||
|
||||
const SegmentedLabel = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
padding: 3px 0;
|
||||
`
|
||||
|
||||
const ModelName = styled.span`
|
||||
font-weight: 500;
|
||||
font-size: 12px;
|
||||
`
|
||||
|
||||
export default MessageGroup
|
||||
export default memo(MessageGroup)
|
||||
|
||||
161
src/renderer/src/pages/home/Messages/MessageGroupMenuBar.tsx
Normal file
161
src/renderer/src/pages/home/Messages/MessageGroupMenuBar.tsx
Normal file
@ -0,0 +1,161 @@
|
||||
import {
|
||||
ColumnHeightOutlined,
|
||||
ColumnWidthOutlined,
|
||||
DeleteOutlined,
|
||||
FolderOutlined,
|
||||
NumberOutlined
|
||||
} from '@ant-design/icons'
|
||||
import ModelAvatar from '@renderer/components/Avatar/ModelAvatar'
|
||||
import { HStack } from '@renderer/components/Layout'
|
||||
import Scrollbar from '@renderer/components/Scrollbar'
|
||||
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
|
||||
import { MultiModelMessageStyle } from '@renderer/store/settings'
|
||||
import { Message, Model } from '@renderer/types'
|
||||
import { Button, Segmented as AntdSegmented } from 'antd'
|
||||
import { FC, memo } from 'react'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import MessageGroupSettings from './MessageGroupSettings'
|
||||
|
||||
interface Props {
|
||||
multiModelMessageStyle: MultiModelMessageStyle
|
||||
setMultiModelMessageStyle: (style: MultiModelMessageStyle) => void
|
||||
messages: Message[]
|
||||
selectedIndex: number
|
||||
setSelectedIndex: (index: number) => void
|
||||
onDelete: () => void
|
||||
}
|
||||
|
||||
const MessageGroupMenuBar: FC<Props> = ({
|
||||
multiModelMessageStyle,
|
||||
setMultiModelMessageStyle,
|
||||
messages,
|
||||
selectedIndex,
|
||||
setSelectedIndex,
|
||||
onDelete
|
||||
}) => {
|
||||
return (
|
||||
<GroupMenuBar $layout={multiModelMessageStyle}>
|
||||
<HStack style={{ alignItems: 'center', flex: 1, overflow: 'hidden' }}>
|
||||
<LayoutContainer>
|
||||
{['fold', 'vertical', 'horizontal', 'grid'].map((layout) => (
|
||||
<LayoutOption
|
||||
key={layout}
|
||||
active={multiModelMessageStyle === layout}
|
||||
onClick={() => setMultiModelMessageStyle(layout as MultiModelMessageStyle)}>
|
||||
{layout === 'fold' ? (
|
||||
<FolderOutlined />
|
||||
) : layout === 'horizontal' ? (
|
||||
<ColumnWidthOutlined />
|
||||
) : layout === 'vertical' ? (
|
||||
<ColumnHeightOutlined />
|
||||
) : (
|
||||
<NumberOutlined />
|
||||
)}
|
||||
</LayoutOption>
|
||||
))}
|
||||
</LayoutContainer>
|
||||
{multiModelMessageStyle === 'fold' && (
|
||||
<ModelsContainer>
|
||||
<Segmented
|
||||
value={selectedIndex.toString()}
|
||||
onChange={(value) => {
|
||||
setSelectedIndex(Number(value))
|
||||
EventEmitter.emit(EVENT_NAMES.LOCATE_MESSAGE + ':' + messages[Number(value)].id, false)
|
||||
}}
|
||||
options={messages.map((message, index) => ({
|
||||
label: (
|
||||
<SegmentedLabel>
|
||||
<ModelAvatar model={message.model as Model} size={20} />
|
||||
<ModelName>{message.model?.name}</ModelName>
|
||||
</SegmentedLabel>
|
||||
),
|
||||
value: index.toString()
|
||||
}))}
|
||||
size="small"
|
||||
/>
|
||||
</ModelsContainer>
|
||||
)}
|
||||
{multiModelMessageStyle === 'grid' && <MessageGroupSettings />}
|
||||
</HStack>
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
icon={<DeleteOutlined style={{ color: 'var(--color-error)' }} />}
|
||||
onClick={onDelete}
|
||||
/>
|
||||
</GroupMenuBar>
|
||||
)
|
||||
}
|
||||
|
||||
const GroupMenuBar = styled.div<{ $layout: MultiModelMessageStyle }>`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 6px 10px;
|
||||
border-radius: 6px;
|
||||
margin-top: 10px;
|
||||
justify-content: space-between;
|
||||
overflow: hidden;
|
||||
border: 0.5px solid var(--color-border);
|
||||
height: 40px;
|
||||
margin-left: ${({ $layout }) => (['horizontal', 'grid'].includes($layout) ? '0' : '40px')};
|
||||
transition: all 0.3s ease;
|
||||
`
|
||||
|
||||
const LayoutContainer = styled.div`
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-direction: row;
|
||||
`
|
||||
|
||||
const LayoutOption = styled.div<{ active: boolean }>`
|
||||
cursor: pointer;
|
||||
padding: 2px 10px;
|
||||
border-radius: 4px;
|
||||
background-color: ${({ active }) => (active ? 'var(--color-background-soft)' : 'transparent')};
|
||||
|
||||
&:hover {
|
||||
background-color: ${({ active }) => (active ? 'var(--color-background-soft)' : 'var(--color-hover)')};
|
||||
}
|
||||
`
|
||||
|
||||
const ModelsContainer = styled(Scrollbar)`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
`
|
||||
|
||||
const Segmented = styled(AntdSegmented)`
|
||||
.ant-segmented-item {
|
||||
background-color: transparent !important;
|
||||
transition: none !important;
|
||||
&:hover {
|
||||
background: transparent !important;
|
||||
}
|
||||
}
|
||||
.ant-segmented-thumb,
|
||||
.ant-segmented-item-selected {
|
||||
background-color: transparent !important;
|
||||
border: 0.5px solid var(--color-border);
|
||||
transition: none !important;
|
||||
}
|
||||
`
|
||||
|
||||
const SegmentedLabel = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
padding: 3px 0;
|
||||
`
|
||||
|
||||
const ModelName = styled.span`
|
||||
font-weight: 500;
|
||||
font-size: 12px;
|
||||
`
|
||||
|
||||
export default memo(MessageGroupMenuBar)
|
||||
Loading…
x
Reference in New Issue
Block a user