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:
parent
462ac39897
commit
00d91ecf01
@ -234,6 +234,9 @@ body,
|
|||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
padding: 10px 15px 0 15px;
|
padding: 10px 15px 0 15px;
|
||||||
}
|
}
|
||||||
|
.message-thought-container {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
.message-user {
|
.message-user {
|
||||||
color: var(--chat-text-user);
|
color: var(--chat-text-user);
|
||||||
.markdown,
|
.markdown,
|
||||||
@ -246,6 +249,16 @@ body,
|
|||||||
background-color: var(--color-white-soft);
|
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 {
|
code {
|
||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
|
|||||||
import { MultiModelMessageStyle } from '@renderer/store/settings'
|
import { MultiModelMessageStyle } from '@renderer/store/settings'
|
||||||
import { Message, Model, Topic } from '@renderer/types'
|
import { Message, Model, Topic } from '@renderer/types'
|
||||||
import { Button, Segmented } from 'antd'
|
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 styled, { css } from 'styled-components'
|
||||||
|
|
||||||
import MessageItem from './Message'
|
import MessageItem from './Message'
|
||||||
@ -37,7 +37,7 @@ const MessageGroup: FC<Props> = ({
|
|||||||
useState<MultiModelMessageStyle>(multiModelMessageStyleSetting)
|
useState<MultiModelMessageStyle>(multiModelMessageStyleSetting)
|
||||||
|
|
||||||
const messageLength = messages.length
|
const messageLength = messages.length
|
||||||
const [selectedIndex, setSelectedIndex] = useState(0)
|
const [selectedIndex, setSelectedIndex] = useState(messageLength - 1)
|
||||||
|
|
||||||
const isGrouped = messageLength > 1
|
const isGrouped = messageLength > 1
|
||||||
|
|
||||||
@ -46,6 +46,12 @@ const MessageGroup: FC<Props> = ({
|
|||||||
askId && onDeleteGroupMessages?.(askId)
|
askId && onDeleteGroupMessages?.(askId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSelectedIndex(messageLength - 1)
|
||||||
|
}, [messageLength])
|
||||||
|
|
||||||
|
const isHorizontal = multiModelMessageStyle === 'horizontal'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GroupContainer $isGrouped={isGrouped} $layout={multiModelMessageStyle}>
|
<GroupContainer $isGrouped={isGrouped} $layout={multiModelMessageStyle}>
|
||||||
<GridContainer $count={messageLength} $layout={multiModelMessageStyle}>
|
<GridContainer $count={messageLength} $layout={multiModelMessageStyle}>
|
||||||
@ -54,7 +60,8 @@ const MessageGroup: FC<Props> = ({
|
|||||||
$layout={multiModelMessageStyle}
|
$layout={multiModelMessageStyle}
|
||||||
$selected={index === selectedIndex}
|
$selected={index === selectedIndex}
|
||||||
$isGrouped={isGrouped}
|
$isGrouped={isGrouped}
|
||||||
key={message.id}>
|
key={message.id}
|
||||||
|
className={message.role === 'assistant' && isHorizontal && isGrouped ? 'group-message-wrapper' : ''}>
|
||||||
<MessageItem
|
<MessageItem
|
||||||
message={message}
|
message={message}
|
||||||
topic={topic}
|
topic={topic}
|
||||||
@ -69,8 +76,8 @@ const MessageGroup: FC<Props> = ({
|
|||||||
))}
|
))}
|
||||||
</GridContainer>
|
</GridContainer>
|
||||||
{isGrouped && (
|
{isGrouped && (
|
||||||
<GroupHeader>
|
<GroupMenuBar className="group-menu-bar">
|
||||||
<HStack style={{ alignItems: 'center' }}>
|
<HStack style={{ alignItems: 'center', flex: 1, overflow: 'hidden' }}>
|
||||||
<LayoutContainer>
|
<LayoutContainer>
|
||||||
{['fold', 'horizontal', 'vertical'].map((layout) => (
|
{['fold', 'horizontal', 'vertical'].map((layout) => (
|
||||||
<LayoutOption
|
<LayoutOption
|
||||||
@ -115,7 +122,7 @@ const MessageGroup: FC<Props> = ({
|
|||||||
icon={<DeleteOutlined style={{ color: 'var(--color-error)' }} />}
|
icon={<DeleteOutlined style={{ color: 'var(--color-error)' }} />}
|
||||||
onClick={onDelete}
|
onClick={onDelete}
|
||||||
/>
|
/>
|
||||||
</GroupHeader>
|
</GroupMenuBar>
|
||||||
)}
|
)}
|
||||||
</GroupContainer>
|
</GroupContainer>
|
||||||
)
|
)
|
||||||
@ -133,6 +140,12 @@ const GridContainer = styled(Scrollbar)<{ $count: number; $layout: MultiModelMes
|
|||||||
minmax(550px, 1fr)
|
minmax(550px, 1fr)
|
||||||
);
|
);
|
||||||
gap: ${({ $layout }) => ($layout === 'horizontal' ? '16px' : '0')};
|
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 {
|
interface MessageWrapperProps {
|
||||||
@ -160,31 +173,24 @@ const MessageWrapper = styled(Scrollbar)<MessageWrapperProps>`
|
|||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
max-height: 600px;
|
max-height: 600px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
margin-bottom: 10px;
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
return ''
|
return ''
|
||||||
}}
|
}}
|
||||||
`
|
`
|
||||||
|
|
||||||
const GroupHeader = styled.div`
|
const GroupMenuBar = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
background-color: var(--color-background-soft);
|
background-color: var(--color-background-soft);
|
||||||
padding: 8px 10px;
|
padding: 6px 10px;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
`
|
overflow: hidden;
|
||||||
|
|
||||||
const ModelsContainer = styled(Scrollbar)`
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: space-between;
|
|
||||||
&::-webkit-scrollbar {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
`
|
`
|
||||||
|
|
||||||
const LayoutContainer = styled.div`
|
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`
|
const SegmentedLabel = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 5px;
|
gap: 5px;
|
||||||
padding: 5px 0;
|
padding: 3px 0;
|
||||||
`
|
`
|
||||||
|
|
||||||
const ModelName = styled.span`
|
const ModelName = styled.span`
|
||||||
|
|||||||
@ -173,16 +173,23 @@ const MessageMenubar: FC<Props> = (props) => {
|
|||||||
const selectedModel = await SelectModelPopup.show({ model })
|
const selectedModel = await SelectModelPopup.show({ model })
|
||||||
if (!selectedModel) return
|
if (!selectedModel) return
|
||||||
|
|
||||||
onEditMessage?.({
|
const _message: Message = {
|
||||||
...message,
|
...message,
|
||||||
content: '',
|
content: '',
|
||||||
reasoning_content: undefined,
|
reasoning_content: undefined,
|
||||||
metrics: undefined,
|
metrics: undefined,
|
||||||
status: 'sending',
|
status: 'sending',
|
||||||
modelId: selectedModel.id || assistantModel?.id || model?.id,
|
modelId: selectedModel.id,
|
||||||
model: selectedModel,
|
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(() => {
|
const onUseful = useCallback(() => {
|
||||||
|
|||||||
@ -23,6 +23,7 @@ const MessageThought: FC<Props> = ({ message }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<CollapseContainer
|
<CollapseContainer
|
||||||
|
className="message-thought-container"
|
||||||
items={[
|
items={[
|
||||||
{
|
{
|
||||||
key: 'thought',
|
key: 'thought',
|
||||||
|
|||||||
@ -68,6 +68,7 @@ const Messages: FC<Props> = ({ assistant, topic, setActiveTopic }) => {
|
|||||||
const onSendMessage = useCallback(
|
const onSendMessage = useCallback(
|
||||||
async (message: Message) => {
|
async (message: Message) => {
|
||||||
const assistantMessages: Message[] = []
|
const assistantMessages: Message[] = []
|
||||||
|
|
||||||
if (message.mentions?.length) {
|
if (message.mentions?.length) {
|
||||||
message.mentions.forEach((m) => {
|
message.mentions.forEach((m) => {
|
||||||
const assistantMessage = getAssistantMessage({ assistant: { ...assistant, model: m }, topic })
|
const assistantMessage = getAssistantMessage({ assistant: { ...assistant, model: m }, topic })
|
||||||
@ -92,6 +93,17 @@ const Messages: FC<Props> = ({ assistant, topic, setActiveTopic }) => {
|
|||||||
[assistant, scrollToBottom, topic]
|
[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 autoRenameTopic = useCallback(async () => {
|
||||||
const _topic = getTopic(assistant, topic.id)
|
const _topic = getTopic(assistant, topic.id)
|
||||||
|
|
||||||
@ -146,6 +158,7 @@ const Messages: FC<Props> = ({ assistant, topic, setActiveTopic }) => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const unsubscribes = [
|
const unsubscribes = [
|
||||||
EventEmitter.on(EVENT_NAMES.SEND_MESSAGE, onSendMessage),
|
EventEmitter.on(EVENT_NAMES.SEND_MESSAGE, onSendMessage),
|
||||||
|
EventEmitter.on(EVENT_NAMES.APPEND_MESSAGE, onAppendMessage),
|
||||||
EventEmitter.on(EVENT_NAMES.RECEIVE_MESSAGE, async () => {
|
EventEmitter.on(EVENT_NAMES.RECEIVE_MESSAGE, async () => {
|
||||||
setTimeout(() => EventEmitter.emit(EVENT_NAMES.AI_AUTO_RENAME), 100)
|
setTimeout(() => EventEmitter.emit(EVENT_NAMES.AI_AUTO_RENAME), 100)
|
||||||
}),
|
}),
|
||||||
@ -215,6 +228,7 @@ const Messages: FC<Props> = ({ assistant, topic, setActiveTopic }) => {
|
|||||||
assistant,
|
assistant,
|
||||||
autoRenameTopic,
|
autoRenameTopic,
|
||||||
messages,
|
messages,
|
||||||
|
onAppendMessage,
|
||||||
onDeleteMessage,
|
onDeleteMessage,
|
||||||
onSendMessage,
|
onSendMessage,
|
||||||
scrollToBottom,
|
scrollToBottom,
|
||||||
|
|||||||
@ -264,9 +264,9 @@ const SettingsTab: FC<Props> = (props) => {
|
|||||||
value={multiModelMessageStyle}
|
value={multiModelMessageStyle}
|
||||||
onChange={(value) => dispatch(setMultiModelMessageStyle(value))}
|
onChange={(value) => dispatch(setMultiModelMessageStyle(value))}
|
||||||
style={{ width: 135 }}>
|
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="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="vertical">{t('message.message.multi_model_style.vertical')}</Select.Option>
|
||||||
<Select.Option value="fold">{t('message.message.multi_model_style.fold')}</Select.Option>
|
|
||||||
</Select>
|
</Select>
|
||||||
</SettingRow>
|
</SettingRow>
|
||||||
<SettingDivider />
|
<SettingDivider />
|
||||||
|
|||||||
@ -4,6 +4,7 @@ export const EventEmitter = new Emittery()
|
|||||||
|
|
||||||
export const EVENT_NAMES = {
|
export const EVENT_NAMES = {
|
||||||
SEND_MESSAGE: 'SEND_MESSAGE',
|
SEND_MESSAGE: 'SEND_MESSAGE',
|
||||||
|
APPEND_MESSAGE: 'APPEND_MESSAGE',
|
||||||
RECEIVE_MESSAGE: 'RECEIVE_MESSAGE',
|
RECEIVE_MESSAGE: 'RECEIVE_MESSAGE',
|
||||||
AI_AUTO_RENAME: 'AI_AUTO_RENAME',
|
AI_AUTO_RENAME: 'AI_AUTO_RENAME',
|
||||||
CLEAR_MESSAGES: 'CLEAR_MESSAGES',
|
CLEAR_MESSAGES: 'CLEAR_MESSAGES',
|
||||||
|
|||||||
@ -103,6 +103,7 @@ export function getAssistantMessage({ assistant, topic }: { assistant: Assistant
|
|||||||
content: '',
|
content: '',
|
||||||
assistantId: assistant.id,
|
assistantId: assistant.id,
|
||||||
topicId: topic.id,
|
topicId: topic.id,
|
||||||
|
model,
|
||||||
modelId: model.id,
|
modelId: model.id,
|
||||||
createdAt: new Date().toISOString(),
|
createdAt: new Date().toISOString(),
|
||||||
type: 'text',
|
type: 'text',
|
||||||
|
|||||||
@ -879,7 +879,7 @@ const migrateConfig = {
|
|||||||
return state
|
return state
|
||||||
},
|
},
|
||||||
'60': (state: RootState) => {
|
'60': (state: RootState) => {
|
||||||
state.settings.multiModelMessageStyle = 'vertical'
|
state.settings.multiModelMessageStyle = 'fold'
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -113,7 +113,7 @@ const initialState: SettingsState = {
|
|||||||
narrowMode: false,
|
narrowMode: false,
|
||||||
enableQuickAssistant: false,
|
enableQuickAssistant: false,
|
||||||
clickTrayToShowQuickAssistant: false,
|
clickTrayToShowQuickAssistant: false,
|
||||||
multiModelMessageStyle: 'horizontal'
|
multiModelMessageStyle: 'fold'
|
||||||
}
|
}
|
||||||
|
|
||||||
const settingsSlice = createSlice({
|
const settingsSlice = createSlice({
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user