feat: enhance message grouping and styling

- Added new styles for message thought containers and group message wrappers to improve UI layout.
- Updated MessageGroup component to dynamically set the selected message index based on message length.
- Introduced a new event for appending messages, enhancing message handling capabilities.
- Refactored MessageMenubar to support the new append message functionality.
- Adjusted multi-model message style setting to 'fold' for better user experience.
- Improved responsiveness of message grid layout for smaller screens.
This commit is contained in:
kangfenmao 2025-01-22 12:03:08 +08:00
parent 462ac39897
commit 00d91ecf01
10 changed files with 77 additions and 25 deletions

View File

@ -234,6 +234,9 @@ body,
border-radius: 8px;
padding: 10px 15px 0 15px;
}
.message-thought-container {
margin-top: 8px;
}
.message-user {
color: var(--chat-text-user);
.markdown,
@ -246,6 +249,16 @@ body,
background-color: var(--color-white-soft);
}
}
.group-message-wrapper {
background-color: var(--color-background);
.message-content-container {
width: 100%;
border: 1px solid var(--color-background-mute);
}
}
.group-menu-bar {
background-color: var(--color-background);
}
code {
color: var(--color-text);
}

View File

@ -7,7 +7,7 @@ import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
import { MultiModelMessageStyle } from '@renderer/store/settings'
import { Message, Model, Topic } from '@renderer/types'
import { Button, Segmented } from 'antd'
import { Dispatch, FC, SetStateAction, useState } from 'react'
import { Dispatch, FC, SetStateAction, useEffect, useState } from 'react'
import styled, { css } from 'styled-components'
import MessageItem from './Message'
@ -37,7 +37,7 @@ const MessageGroup: FC<Props> = ({
useState<MultiModelMessageStyle>(multiModelMessageStyleSetting)
const messageLength = messages.length
const [selectedIndex, setSelectedIndex] = useState(0)
const [selectedIndex, setSelectedIndex] = useState(messageLength - 1)
const isGrouped = messageLength > 1
@ -46,6 +46,12 @@ const MessageGroup: FC<Props> = ({
askId && onDeleteGroupMessages?.(askId)
}
useEffect(() => {
setSelectedIndex(messageLength - 1)
}, [messageLength])
const isHorizontal = multiModelMessageStyle === 'horizontal'
return (
<GroupContainer $isGrouped={isGrouped} $layout={multiModelMessageStyle}>
<GridContainer $count={messageLength} $layout={multiModelMessageStyle}>
@ -54,7 +60,8 @@ const MessageGroup: FC<Props> = ({
$layout={multiModelMessageStyle}
$selected={index === selectedIndex}
$isGrouped={isGrouped}
key={message.id}>
key={message.id}
className={message.role === 'assistant' && isHorizontal && isGrouped ? 'group-message-wrapper' : ''}>
<MessageItem
message={message}
topic={topic}
@ -69,8 +76,8 @@ const MessageGroup: FC<Props> = ({
))}
</GridContainer>
{isGrouped && (
<GroupHeader>
<HStack style={{ alignItems: 'center' }}>
<GroupMenuBar className="group-menu-bar">
<HStack style={{ alignItems: 'center', flex: 1, overflow: 'hidden' }}>
<LayoutContainer>
{['fold', 'horizontal', 'vertical'].map((layout) => (
<LayoutOption
@ -115,7 +122,7 @@ const MessageGroup: FC<Props> = ({
icon={<DeleteOutlined style={{ color: 'var(--color-error)' }} />}
onClick={onDelete}
/>
</GroupHeader>
</GroupMenuBar>
)}
</GroupContainer>
)
@ -133,6 +140,12 @@ const GridContainer = styled(Scrollbar)<{ $count: number; $layout: MultiModelMes
minmax(550px, 1fr)
);
gap: ${({ $layout }) => ($layout === 'horizontal' ? '16px' : '0')};
@media (max-width: 800px) {
grid-template-columns: repeat(
${(props) => (['fold', 'vertical'].includes(props.$layout) ? 1 : props.$count)},
minmax(400px, 1fr)
);
}
`
interface MessageWrapperProps {
@ -160,31 +173,24 @@ const MessageWrapper = styled(Scrollbar)<MessageWrapperProps>`
border-radius: 6px;
max-height: 600px;
overflow-y: auto;
margin-bottom: 10px;
`
}
return ''
}}
`
const GroupHeader = styled.div`
const GroupMenuBar = styled.div`
display: flex;
flex-direction: row;
align-items: center;
gap: 10px;
background-color: var(--color-background-soft);
padding: 8px 10px;
padding: 6px 10px;
border-radius: 6px;
margin-top: 10px;
justify-content: space-between;
`
const ModelsContainer = styled(Scrollbar)`
display: flex;
flex-direction: column;
justify-content: space-between;
&::-webkit-scrollbar {
display: none;
}
overflow: hidden;
`
const LayoutContainer = styled.div`
@ -205,11 +211,20 @@ const LayoutOption = styled.div<{ active: boolean }>`
}
`
const ModelsContainer = styled(Scrollbar)`
display: flex;
flex-direction: column;
justify-content: space-between;
&::-webkit-scrollbar {
display: none;
}
`
const SegmentedLabel = styled.div`
display: flex;
align-items: center;
gap: 5px;
padding: 5px 0;
padding: 3px 0;
`
const ModelName = styled.span`

View File

@ -173,16 +173,23 @@ const MessageMenubar: FC<Props> = (props) => {
const selectedModel = await SelectModelPopup.show({ model })
if (!selectedModel) return
onEditMessage?.({
const _message: Message = {
...message,
content: '',
reasoning_content: undefined,
metrics: undefined,
status: 'sending',
modelId: selectedModel.id || assistantModel?.id || model?.id,
modelId: selectedModel.id,
model: selectedModel,
translatedContent: undefined
})
translatedContent: undefined,
metadata: undefined
}
if (message.askId && message.model) {
return EventEmitter.emit(EVENT_NAMES.APPEND_MESSAGE, { ..._message, id: uuid() })
}
onEditMessage?.(_message)
}
const onUseful = useCallback(() => {

View File

@ -23,6 +23,7 @@ const MessageThought: FC<Props> = ({ message }) => {
return (
<CollapseContainer
className="message-thought-container"
items={[
{
key: 'thought',

View File

@ -68,6 +68,7 @@ const Messages: FC<Props> = ({ assistant, topic, setActiveTopic }) => {
const onSendMessage = useCallback(
async (message: Message) => {
const assistantMessages: Message[] = []
if (message.mentions?.length) {
message.mentions.forEach((m) => {
const assistantMessage = getAssistantMessage({ assistant: { ...assistant, model: m }, topic })
@ -92,6 +93,17 @@ const Messages: FC<Props> = ({ assistant, topic, setActiveTopic }) => {
[assistant, scrollToBottom, topic]
)
const onAppendMessage = useCallback(
(message: Message) => {
setMessages((prev) => {
const messages = prev.concat([message])
db.topics.put({ id: topic.id, messages })
return messages
})
},
[topic.id]
)
const autoRenameTopic = useCallback(async () => {
const _topic = getTopic(assistant, topic.id)
@ -146,6 +158,7 @@ const Messages: FC<Props> = ({ assistant, topic, setActiveTopic }) => {
useEffect(() => {
const unsubscribes = [
EventEmitter.on(EVENT_NAMES.SEND_MESSAGE, onSendMessage),
EventEmitter.on(EVENT_NAMES.APPEND_MESSAGE, onAppendMessage),
EventEmitter.on(EVENT_NAMES.RECEIVE_MESSAGE, async () => {
setTimeout(() => EventEmitter.emit(EVENT_NAMES.AI_AUTO_RENAME), 100)
}),
@ -215,6 +228,7 @@ const Messages: FC<Props> = ({ assistant, topic, setActiveTopic }) => {
assistant,
autoRenameTopic,
messages,
onAppendMessage,
onDeleteMessage,
onSendMessage,
scrollToBottom,

View File

@ -264,9 +264,9 @@ const SettingsTab: FC<Props> = (props) => {
value={multiModelMessageStyle}
onChange={(value) => dispatch(setMultiModelMessageStyle(value))}
style={{ width: 135 }}>
<Select.Option value="fold">{t('message.message.multi_model_style.fold')}</Select.Option>
<Select.Option value="horizontal">{t('message.message.multi_model_style.horizontal')}</Select.Option>
<Select.Option value="vertical">{t('message.message.multi_model_style.vertical')}</Select.Option>
<Select.Option value="fold">{t('message.message.multi_model_style.fold')}</Select.Option>
</Select>
</SettingRow>
<SettingDivider />

View File

@ -4,6 +4,7 @@ export const EventEmitter = new Emittery()
export const EVENT_NAMES = {
SEND_MESSAGE: 'SEND_MESSAGE',
APPEND_MESSAGE: 'APPEND_MESSAGE',
RECEIVE_MESSAGE: 'RECEIVE_MESSAGE',
AI_AUTO_RENAME: 'AI_AUTO_RENAME',
CLEAR_MESSAGES: 'CLEAR_MESSAGES',

View File

@ -103,6 +103,7 @@ export function getAssistantMessage({ assistant, topic }: { assistant: Assistant
content: '',
assistantId: assistant.id,
topicId: topic.id,
model,
modelId: model.id,
createdAt: new Date().toISOString(),
type: 'text',

View File

@ -879,7 +879,7 @@ const migrateConfig = {
return state
},
'60': (state: RootState) => {
state.settings.multiModelMessageStyle = 'vertical'
state.settings.multiModelMessageStyle = 'fold'
return state
}
}

View File

@ -113,7 +113,7 @@ const initialState: SettingsState = {
narrowMode: false,
enableQuickAssistant: false,
clickTrayToShowQuickAssistant: false,
multiModelMessageStyle: 'horizontal'
multiModelMessageStyle: 'fold'
}
const settingsSlice = createSlice({