Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add a new grid mode for message display. #1626

Merged
merged 9 commits into from
Feb 17, 2025
3 changes: 3 additions & 0 deletions src/renderer/src/i18n/locales/en-us.json
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,7 @@
"message.multi_model_style.fold": "Fold",
"message.multi_model_style.horizontal": "Horizontal",
"message.multi_model_style.vertical": "Vertical",
"message.multi_model_style.grid": "Grid",
"message.style": "Message style",
"message.style.bubble": "Bubble",
"message.style.plain": "Plain",
Expand Down Expand Up @@ -636,6 +637,8 @@
"messages.input.title": "Input Settings",
"messages.markdown_rendering_input_message": "Markdown render input message",
"messages.math_engine": "Math engine",
"messages.grid_columns": "Message grid display columns",
"messages.grid_popover_trigger": "Grid detail trigger",
"messages.metrics": "{{time_first_token_millsec}}ms to first token | {{token_speed}} tok/sec",
"messages.model.title": "Model Settings",
"messages.title": "Message Settings",
Expand Down
3 changes: 3 additions & 0 deletions src/renderer/src/i18n/locales/ja-jp.json
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,7 @@
"message.multi_model_style.fold": "折りたたむ",
"message.multi_model_style.horizontal": "水平",
"message.multi_model_style.vertical": "垂直",
"message.multi_model_style.grid": "グリッド",
"message.style": "メッセージスタイル",
"message.style.bubble": "バブル",
"message.style.plain": "プレーン",
Expand Down Expand Up @@ -636,6 +637,8 @@
"messages.input.title": "入力設定",
"messages.markdown_rendering_input_message": "Markdownで入力メッセージをレンダリング",
"messages.math_engine": "数式エンジン",
"messages.grid_columns": "メッセージグリッドの表示列数",
"messages.grid_popover_trigger": "グリッド詳細トリガー",
"messages.metrics": "最初のトークンまでの時間 {{time_first_token_millsec}}ms | トークン速度 {{token_speed}} tok/sec",
"messages.model.title": "モデル設定",
"messages.title": "メッセージ設定",
Expand Down
3 changes: 3 additions & 0 deletions src/renderer/src/i18n/locales/ru-ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,7 @@
"message.multi_model_style.fold": "Свернуть",
"message.multi_model_style.horizontal": "Горизонтальный",
"message.multi_model_style.vertical": "Вертикальный",
"message.multi_model_style.grid": "клетчатый вид",
"message.style": "Стиль сообщения",
"message.style.bubble": "Пузырь",
"message.style.plain": "Простой",
Expand Down Expand Up @@ -637,6 +638,8 @@
"messages.math_engine": "Математический движок",
"messages.metrics": "{{time_first_token_millsec}}ms до первого токена | {{token_speed}} tok/sec",
"messages.model.title": "Настройки модели",
"messages.grid_columns": "Количество столбцов сетки сообщений",
"messages.grid_popover_trigger": "Триггер для отображения подробной информации в сетке",
"messages.title": "Настройки сообщений",
"messages.use_serif_font": "Использовать serif шрифт",
"model": "Модель по умолчанию",
Expand Down
3 changes: 3 additions & 0 deletions src/renderer/src/i18n/locales/zh-cn.json
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,7 @@
"message.multi_model_style.fold": "折叠",
"message.multi_model_style.horizontal": "水平",
"message.multi_model_style.vertical": "垂直",
"message.multi_model_style.grid": "网格",
"message.style": "消息样式",
"message.style.bubble": "气泡",
"message.style.plain": "简洁",
Expand Down Expand Up @@ -636,6 +637,8 @@
"messages.input.title": "输入设置",
"messages.markdown_rendering_input_message": "Markdown 渲染输入消息",
"messages.math_engine": "数学公式引擎",
"messages.grid_columns": "消息网格展示列数",
"messages.grid_popover_trigger": "网格详情触发",
"messages.metrics": "首字时延 {{time_first_token_millsec}}ms | 每秒 {{token_speed}} tokens",
"messages.model.title": "模型设置",
"messages.title": "消息设置",
Expand Down
3 changes: 3 additions & 0 deletions src/renderer/src/i18n/locales/zh-tw.json
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,7 @@
"message.multi_model_style.fold": "折疊",
"message.multi_model_style.horizontal": "水平",
"message.multi_model_style.vertical": "垂直",
"message.multi_model_style.grid": "网格",
"message.style": "消息樣式",
"message.style.bubble": "氣泡",
"message.style.plain": "簡潔",
Expand Down Expand Up @@ -635,6 +636,8 @@
"messages.input.show_estimated_tokens": "顯示預估 Token 數",
"messages.input.title": "輸入設定",
"messages.math_engine": "Markdown 渲染輸入訊息",
"messages.grid_columns": "消息網格展示列數",
"messages.grid_popover_trigger": "網格詳情觸發",
"messages.metrics": "首字時延 {{time_first_token_millsec}}ms | 每秒 {{token_speed}} tokens",
"messages.model.title": "模型設定",
"messages.title": "訊息設定",
Expand Down
150 changes: 119 additions & 31 deletions src/renderer/src/pages/home/Messages/MessageGroup.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import { ColumnHeightOutlined, ColumnWidthOutlined, DeleteOutlined, FolderOutlined } from '@ant-design/icons'
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, Segmented as AntdSegmented } from 'antd'
import { Button, Popover, Segmented as AntdSegmented } from 'antd'
import { Dispatch, FC, SetStateAction, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import styled, { css } from 'styled-components'
Expand All @@ -32,7 +38,7 @@ const MessageGroup: FC<Props> = ({
onGetMessages,
onDeleteGroupMessages
}) => {
const { multiModelMessageStyle: multiModelMessageStyleSetting } = useSettings()
const { multiModelMessageStyle: multiModelMessageStyleSetting, gridColumns, gridPopoverTrigger } = useSettings()
const { t } = useTranslation()

const [multiModelMessageStyle, setMultiModelMessageStyle] =
Expand Down Expand Up @@ -67,33 +73,89 @@ const MessageGroup: FC<Props> = ({

return (
<GroupContainer $isGrouped={isGrouped} $layout={multiModelMessageStyle}>
<GridContainer $count={messageLength} $layout={multiModelMessageStyle}>
{messages.map((message, index) => (
<MessageWrapper
$layout={multiModelMessageStyle}
$selected={index === selectedIndex}
$isGrouped={isGrouped}
key={message.id}
className={message.role === 'assistant' && isHorizontal && isGrouped ? 'group-message-wrapper' : ''}>
<MessageItem
isGrouped={isGrouped}
message={message}
topic={topic}
index={message.index}
hidePresetMessages={hidePresetMessages}
style={{ paddingTop: isGrouped && multiModelMessageStyle === 'horizontal' ? 0 : 15 }}
onSetMessages={onSetMessages}
onDeleteMessage={onDeleteMessage}
onGetMessages={onGetMessages}
/>
</MessageWrapper>
))}
<GridContainer $count={messageLength} $layout={multiModelMessageStyle} $gridColumns={gridColumns}>
{messages.map((message, index) =>
multiModelMessageStyle === 'grid' && message.role === 'assistant' && isGrouped ? (
<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}
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>
) : (
<MessageWrapper
$layout={multiModelMessageStyle}
$selected={index === selectedIndex}
$isGrouped={isGrouped}
key={message.id}
className={message.role === 'assistant' && isHorizontal && isGrouped ? 'group-message-wrapper' : ''}>
<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>
)
)}
</GridContainer>
{isGrouped && (
<GroupMenuBar className="group-menu-bar" $layout={multiModelMessageStyle}>
<HStack style={{ alignItems: 'center', flex: 1, overflow: 'hidden' }}>
<LayoutContainer>
{['fold', 'vertical', 'horizontal'].map((layout) => (
{['fold', 'vertical', 'horizontal', 'grid'].map((layout) => (
<LayoutOption
key={layout}
active={multiModelMessageStyle === layout}
Expand All @@ -102,8 +164,10 @@ const MessageGroup: FC<Props> = ({
<FolderOutlined />
) : layout === 'horizontal' ? (
<ColumnWidthOutlined />
) : (
) : layout === 'vertical' ? (
<ColumnHeightOutlined />
) : (
<NumberOutlined />
)}
</LayoutOption>
))}
Expand Down Expand Up @@ -143,30 +207,38 @@ const MessageGroup: FC<Props> = ({
}

const GroupContainer = styled.div<{ $isGrouped: boolean; $layout: MultiModelMessageStyle }>`
padding-top: ${({ $isGrouped, $layout }) => ($isGrouped && $layout === 'horizontal' ? '15px' : '0')};
padding-top: ${({ $isGrouped, $layout }) => ($isGrouped && 'horizontal' === $layout ? '15px' : '0')};
`

const GridContainer = styled.div<{ $count: number; $layout: MultiModelMessageStyle }>`
const GridContainer = styled.div<{ $count: number; $layout: MultiModelMessageStyle; $gridColumns: number }>`
width: 100%;
display: grid;
grid-template-columns: repeat(
${(props) => (['fold', 'vertical'].includes(props.$layout) ? 1 : props.$count)},
${({ $layout, $count }) => (['fold', 'vertical'].includes($layout) ? 1 : $count)},
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)},
${({ $layout, $count }) => (['fold', 'vertical'].includes($layout) ? 1 : $count)},
minmax(400px, 1fr)
);
}
overflow-y: auto;
${({ $gridColumns, $layout, $count }) =>
$layout === 'grid' &&
css`
grid-template-columns: repeat(${$count > 1 ? $gridColumns || 2 : 1}, minmax(0, 1fr));
grid-template-rows: auto;
gap: 16px;
`}
`

interface MessageWrapperProps {
$layout: 'fold' | 'horizontal' | 'vertical'
$layout: 'fold' | 'horizontal' | 'vertical' | 'grid'
$selected: boolean
$isGrouped: boolean
$isInPopover?: boolean
}

const MessageWrapper = styled(Scrollbar)<MessageWrapperProps>`
Expand All @@ -193,6 +265,22 @@ const MessageWrapper = styled(Scrollbar)<MessageWrapperProps>`
}
return ''
}}

${({ $layout, $isInPopover, $isGrouped }) =>
$layout === 'grid' && $isGrouped
? css`
max-height: ${$isInPopover ? '50vh' : '300px'};
overflow-y: auto;
border: 0.5px solid var(--color-border);
padding: 10px;
border-radius: 6px;
`
: css`
overflow-y: auto;
border: 0.5px solid transparent;
padding: 0 10px;
border-radius: 6px;
`}
`

const GroupMenuBar = styled.div<{ $layout: MultiModelMessageStyle }>`
Expand Down
Loading