diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index a39c26378f..5aabfc9221 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -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", @@ -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", diff --git a/src/renderer/src/i18n/locales/ja-jp.json b/src/renderer/src/i18n/locales/ja-jp.json index 94ee718208..25774cc60b 100644 --- a/src/renderer/src/i18n/locales/ja-jp.json +++ b/src/renderer/src/i18n/locales/ja-jp.json @@ -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": "プレーン", @@ -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": "メッセージ設定", diff --git a/src/renderer/src/i18n/locales/ru-ru.json b/src/renderer/src/i18n/locales/ru-ru.json index cdf67b7486..79ea0c77fe 100644 --- a/src/renderer/src/i18n/locales/ru-ru.json +++ b/src/renderer/src/i18n/locales/ru-ru.json @@ -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": "Простой", @@ -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": "Модель по умолчанию", diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index bccc8b19ae..61ada8dba1 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -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": "简洁", @@ -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": "消息设置", diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index 2c6e97dd7d..425aa49230 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -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": "簡潔", @@ -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": "訊息設定", diff --git a/src/renderer/src/pages/home/Messages/MessageGroup.tsx b/src/renderer/src/pages/home/Messages/MessageGroup.tsx index 3b8488bfe1..629185edf9 100644 --- a/src/renderer/src/pages/home/Messages/MessageGroup.tsx +++ b/src/renderer/src/pages/home/Messages/MessageGroup.tsx @@ -1,4 +1,10 @@ -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' @@ -6,7 +12,7 @@ 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' @@ -32,7 +38,7 @@ const MessageGroup: FC = ({ onGetMessages, onDeleteGroupMessages }) => { - const { multiModelMessageStyle: multiModelMessageStyleSetting } = useSettings() + const { multiModelMessageStyle: multiModelMessageStyleSetting, gridColumns, gridPopoverTrigger } = useSettings() const { t } = useTranslation() const [multiModelMessageStyle, setMultiModelMessageStyle] = @@ -67,33 +73,89 @@ const MessageGroup: FC = ({ return ( - - {messages.map((message, index) => ( - - - - ))} + + {messages.map((message, index) => + multiModelMessageStyle === 'grid' && message.role === 'assistant' && isGrouped ? ( + + + + } + trigger={gridPopoverTrigger} + styles={{ root: { maxWidth: '60vw', minWidth: '550px', overflowY: 'auto', zIndex: 1000 } }} + getPopupContainer={(triggerNode) => triggerNode.parentNode as HTMLElement} + key={message.id}> + + + + + ) : ( + + + + ) + )} {isGrouped && ( - {['fold', 'vertical', 'horizontal'].map((layout) => ( + {['fold', 'vertical', 'horizontal', 'grid'].map((layout) => ( = ({ ) : layout === 'horizontal' ? ( - ) : ( + ) : layout === 'vertical' ? ( + ) : ( + )} ))} @@ -143,30 +207,38 @@ const MessageGroup: FC = ({ } 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)` @@ -193,6 +265,22 @@ const MessageWrapper = styled(Scrollbar)` } 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 }>` diff --git a/src/renderer/src/pages/home/Messages/MessageMenubar.tsx b/src/renderer/src/pages/home/Messages/MessageMenubar.tsx index 9cd8b3aefb..0b3e6f05ff 100644 --- a/src/renderer/src/pages/home/Messages/MessageMenubar.tsx +++ b/src/renderer/src/pages/home/Messages/MessageMenubar.tsx @@ -60,12 +60,16 @@ const MessageMenubar: FC = (props) => { const isUserMessage = message.role === 'user' - const onCopy = useCallback(() => { - navigator.clipboard.writeText(removeTrailingDoubleSpaces(message.content)) - window.message.success({ content: t('message.copied'), key: 'copy-message' }) - setCopied(true) - setTimeout(() => setCopied(false), 2000) - }, [message.content, t]) + const onCopy = useCallback( + (e: React.MouseEvent) => { + e.stopPropagation() + navigator.clipboard.writeText(removeTrailingDoubleSpaces(message.content)) + window.message.success({ content: t('message.copied'), key: 'copy-message' }) + setCopied(true) + setTimeout(() => setCopied(false), 2000) + }, + [message.content, t] + ) const onNewBranch = useCallback(async () => { await modelGenerating() @@ -195,14 +199,16 @@ const MessageMenubar: FC = (props) => { [message, onEdit, onNewBranch, t] ) - const onRegenerate = async () => { + const onRegenerate = async (e: React.MouseEvent | undefined) => { + e?.stopPropagation?.() await modelGenerating() const selectedModel = isGrouped ? model : assistantModel const _message = resetAssistantMessage(message, selectedModel) onEditMessage?.(_message) } - const onMentionModel = async () => { + const onMentionModel = async (e: React.MouseEvent) => { + e.stopPropagation() await modelGenerating() const selectedModel = await SelectModelPopup.show({ model }) if (!selectedModel) return @@ -216,9 +222,13 @@ const MessageMenubar: FC = (props) => { onEditMessage?.(_message) } - const onUseful = useCallback(() => { - onEditMessage?.({ ...message, useful: !message.useful }) - }, [message, onEditMessage]) + const onUseful = useCallback( + (e: React.MouseEvent) => { + e.stopPropagation() + onEditMessage?.({ ...message, useful: !message.useful }) + }, + [message, onEditMessage] + ) return ( @@ -270,13 +280,14 @@ const MessageMenubar: FC = (props) => { key: 'translate-close', onClick: () => onEditMessage?.({ ...message, translatedContent: undefined }) } - ] + ], + onClick: (e) => e.domEvent.stopPropagation() }} trigger={['click']} placement="topRight" arrow> - + e.stopPropagation()}> @@ -298,14 +309,25 @@ const MessageMenubar: FC = (props) => { onDeleteMessage?.(message) : undefined}> + onClick={ + isGrouped + ? (e) => { + e.stopPropagation() + onDeleteMessage?.(message) + } + : (e) => e.stopPropagation() + }> {!isUserMessage && ( - - + e.domEvent.stopPropagation() }} + trigger={['click']} + placement="topRight" + arrow> + e.stopPropagation()}> diff --git a/src/renderer/src/pages/home/Tabs/SettingsTab.tsx b/src/renderer/src/pages/home/Tabs/SettingsTab.tsx index 2df3ff66d4..8828716c0c 100644 --- a/src/renderer/src/pages/home/Tabs/SettingsTab.tsx +++ b/src/renderer/src/pages/home/Tabs/SettingsTab.tsx @@ -19,6 +19,8 @@ import { setCodeShowLineNumbers, setCodeStyle, setFontSize, + setGridColumns, + setGridPopoverTrigger, setMathEngine, setMessageFont, setMessageStyle, @@ -42,13 +44,14 @@ interface Props { const SettingsTab: FC = (props) => { const { assistant, updateAssistantSettings, updateAssistant } = useAssistant(props.assistant.id) - const { messageStyle, codeStyle, fontSize, language } = useSettings() + const { messageStyle, codeStyle, fontSize, language, gridColumns } = useSettings() const [temperature, setTemperature] = useState(assistant?.settings?.temperature ?? DEFAULT_TEMPERATURE) const [contextCount, setContextCount] = useState(assistant?.settings?.contextCount ?? DEFAULT_CONTEXTCOUNT) const [enableMaxTokens, setEnableMaxTokens] = useState(assistant?.settings?.enableMaxTokens ?? false) const [maxTokens, setMaxTokens] = useState(assistant?.settings?.maxTokens ?? 0) const [fontSizeValue, setFontSizeValue] = useState(fontSize) + const [gridColumnsValue, setGridColumnsValue] = useState(gridColumns) const [streamOutput, setStreamOutput] = useState(assistant?.settings?.streamOutput ?? true) const { t } = useTranslation() @@ -69,7 +72,8 @@ const SettingsTab: FC = (props) => { mathEngine, autoTranslateWithSpace, pasteLongTextThreshold, - multiModelMessageStyle + multiModelMessageStyle, + gridPopoverTrigger } = useSettings() const onUpdateAssistantSettings = (settings: Partial) => { @@ -283,6 +287,7 @@ const SettingsTab: FC = (props) => { {t('message.message.multi_model_style.fold')} {t('message.message.multi_model_style.vertical')} {t('message.message.multi_model_style.horizontal')} + {t('message.message.multi_model_style.grid')} @@ -313,6 +318,34 @@ const SettingsTab: FC = (props) => { + + {t('settings.messages.grid_popover_trigger')} + + + + + {t('settings.messages.grid_columns')} + + + + setGridColumnsValue(value)} + onChangeComplete={(value) => dispatch(setGridColumns(value))} + min={2} + max={9} + step={1} + /> + + + {t('settings.font_size.title')} diff --git a/src/renderer/src/store/settings.ts b/src/renderer/src/store/settings.ts index 18dba29f95..03b5165811 100644 --- a/src/renderer/src/store/settings.ts +++ b/src/renderer/src/store/settings.ts @@ -44,6 +44,8 @@ export interface SettingsState { mathEngine: 'MathJax' | 'KaTeX' messageStyle: 'plain' | 'bubble' codeStyle: CodeStyleVarious + gridColumns: number + gridPopoverTrigger: 'hover' | 'click' // webdav 配置 host, user, pass, path webdavHost: string webdavUser: string @@ -69,7 +71,7 @@ export interface SettingsState { notionApiKey: string | null } -export type MultiModelMessageStyle = 'horizontal' | 'vertical' | 'fold' +export type MultiModelMessageStyle = 'horizontal' | 'vertical' | 'fold' | 'grid' const initialState: SettingsState = { showAssistants: true, @@ -99,6 +101,8 @@ const initialState: SettingsState = { mathEngine: 'KaTeX', messageStyle: 'plain', codeStyle: 'auto', + gridColumns: 2, + gridPopoverTrigger: 'hover', webdavHost: '', webdavUser: '', webdavPass: '', @@ -224,6 +228,12 @@ const settingsSlice = createSlice({ setMathEngine: (state, action: PayloadAction<'MathJax' | 'KaTeX'>) => { state.mathEngine = action.payload }, + setGridColumns: (state, action: PayloadAction) => { + state.gridColumns = action.payload + }, + setGridPopoverTrigger: (state, action: PayloadAction<'hover' | 'click'>) => { + state.gridPopoverTrigger = action.payload + }, setMessageStyle: (state, action: PayloadAction<'plain' | 'bubble'>) => { state.messageStyle = action.payload }, @@ -265,7 +275,7 @@ const settingsSlice = createSlice({ setEnableQuickAssistant: (state, action: PayloadAction) => { state.enableQuickAssistant = action.payload }, - setMultiModelMessageStyle: (state, action: PayloadAction<'horizontal' | 'vertical' | 'fold'>) => { + setMultiModelMessageStyle: (state, action: PayloadAction<'horizontal' | 'vertical' | 'fold' | 'grid'>) => { state.multiModelMessageStyle = action.payload }, setNotionDatabaseID: (state, action: PayloadAction) => { @@ -310,6 +320,8 @@ export const { setCodeShowLineNumbers, setCodeCollapsible, setMathEngine, + setGridColumns, + setGridPopoverTrigger, setMessageStyle, setCodeStyle, setTranslateModelPrompt,