mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-24 02:20:10 +08:00
feat: Add a new grid mode for message display. (#1626)
* chore(version): 0.9.23 * feat(renderer): 新增网格模式的消息展示方式 * feat(message): 新增消息网格展示相关设置 * 根据 gridPopoverTrigger 属性动态设置消息分组的样式 * 在 MessageMenubar 组件中,各个按钮 click 事件阻止事件冒泡,避免打开 popover * 多模型回答样式添加网格模式并优化消息样式 --------- Co-authored-by: kangfenmao <kangfenmao@qq.com>
This commit is contained in:
parent
e3115d00bf
commit
bad2f15c1f
@ -364,6 +364,7 @@
|
|||||||
"message.multi_model_style.fold": "Fold",
|
"message.multi_model_style.fold": "Fold",
|
||||||
"message.multi_model_style.horizontal": "Horizontal",
|
"message.multi_model_style.horizontal": "Horizontal",
|
||||||
"message.multi_model_style.vertical": "Vertical",
|
"message.multi_model_style.vertical": "Vertical",
|
||||||
|
"message.multi_model_style.grid": "Grid",
|
||||||
"message.style": "Message style",
|
"message.style": "Message style",
|
||||||
"message.style.bubble": "Bubble",
|
"message.style.bubble": "Bubble",
|
||||||
"message.style.plain": "Plain",
|
"message.style.plain": "Plain",
|
||||||
@ -636,6 +637,8 @@
|
|||||||
"messages.input.title": "Input Settings",
|
"messages.input.title": "Input Settings",
|
||||||
"messages.markdown_rendering_input_message": "Markdown render input message",
|
"messages.markdown_rendering_input_message": "Markdown render input message",
|
||||||
"messages.math_engine": "Math engine",
|
"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.metrics": "{{time_first_token_millsec}}ms to first token | {{token_speed}} tok/sec",
|
||||||
"messages.model.title": "Model Settings",
|
"messages.model.title": "Model Settings",
|
||||||
"messages.title": "Message Settings",
|
"messages.title": "Message Settings",
|
||||||
|
|||||||
@ -363,6 +363,7 @@
|
|||||||
"message.multi_model_style.fold": "折りたたむ",
|
"message.multi_model_style.fold": "折りたたむ",
|
||||||
"message.multi_model_style.horizontal": "水平",
|
"message.multi_model_style.horizontal": "水平",
|
||||||
"message.multi_model_style.vertical": "垂直",
|
"message.multi_model_style.vertical": "垂直",
|
||||||
|
"message.multi_model_style.grid": "グリッド",
|
||||||
"message.style": "メッセージスタイル",
|
"message.style": "メッセージスタイル",
|
||||||
"message.style.bubble": "バブル",
|
"message.style.bubble": "バブル",
|
||||||
"message.style.plain": "プレーン",
|
"message.style.plain": "プレーン",
|
||||||
@ -636,6 +637,8 @@
|
|||||||
"messages.input.title": "入力設定",
|
"messages.input.title": "入力設定",
|
||||||
"messages.markdown_rendering_input_message": "Markdownで入力メッセージをレンダリング",
|
"messages.markdown_rendering_input_message": "Markdownで入力メッセージをレンダリング",
|
||||||
"messages.math_engine": "数式エンジン",
|
"messages.math_engine": "数式エンジン",
|
||||||
|
"messages.grid_columns": "メッセージグリッドの表示列数",
|
||||||
|
"messages.grid_popover_trigger": "グリッド詳細トリガー",
|
||||||
"messages.metrics": "最初のトークンまでの時間 {{time_first_token_millsec}}ms | トークン速度 {{token_speed}} tok/sec",
|
"messages.metrics": "最初のトークンまでの時間 {{time_first_token_millsec}}ms | トークン速度 {{token_speed}} tok/sec",
|
||||||
"messages.model.title": "モデル設定",
|
"messages.model.title": "モデル設定",
|
||||||
"messages.title": "メッセージ設定",
|
"messages.title": "メッセージ設定",
|
||||||
|
|||||||
@ -364,6 +364,7 @@
|
|||||||
"message.multi_model_style.fold": "Свернуть",
|
"message.multi_model_style.fold": "Свернуть",
|
||||||
"message.multi_model_style.horizontal": "Горизонтальный",
|
"message.multi_model_style.horizontal": "Горизонтальный",
|
||||||
"message.multi_model_style.vertical": "Вертикальный",
|
"message.multi_model_style.vertical": "Вертикальный",
|
||||||
|
"message.multi_model_style.grid": "клетчатый вид",
|
||||||
"message.style": "Стиль сообщения",
|
"message.style": "Стиль сообщения",
|
||||||
"message.style.bubble": "Пузырь",
|
"message.style.bubble": "Пузырь",
|
||||||
"message.style.plain": "Простой",
|
"message.style.plain": "Простой",
|
||||||
@ -637,6 +638,8 @@
|
|||||||
"messages.math_engine": "Математический движок",
|
"messages.math_engine": "Математический движок",
|
||||||
"messages.metrics": "{{time_first_token_millsec}}ms до первого токена | {{token_speed}} tok/sec",
|
"messages.metrics": "{{time_first_token_millsec}}ms до первого токена | {{token_speed}} tok/sec",
|
||||||
"messages.model.title": "Настройки модели",
|
"messages.model.title": "Настройки модели",
|
||||||
|
"messages.grid_columns": "Количество столбцов сетки сообщений",
|
||||||
|
"messages.grid_popover_trigger": "Триггер для отображения подробной информации в сетке",
|
||||||
"messages.title": "Настройки сообщений",
|
"messages.title": "Настройки сообщений",
|
||||||
"messages.use_serif_font": "Использовать serif шрифт",
|
"messages.use_serif_font": "Использовать serif шрифт",
|
||||||
"model": "Модель по умолчанию",
|
"model": "Модель по умолчанию",
|
||||||
|
|||||||
@ -366,6 +366,7 @@
|
|||||||
"message.multi_model_style.fold": "折叠",
|
"message.multi_model_style.fold": "折叠",
|
||||||
"message.multi_model_style.horizontal": "水平",
|
"message.multi_model_style.horizontal": "水平",
|
||||||
"message.multi_model_style.vertical": "垂直",
|
"message.multi_model_style.vertical": "垂直",
|
||||||
|
"message.multi_model_style.grid": "网格",
|
||||||
"message.style": "消息样式",
|
"message.style": "消息样式",
|
||||||
"message.style.bubble": "气泡",
|
"message.style.bubble": "气泡",
|
||||||
"message.style.plain": "简洁",
|
"message.style.plain": "简洁",
|
||||||
@ -636,6 +637,8 @@
|
|||||||
"messages.input.title": "输入设置",
|
"messages.input.title": "输入设置",
|
||||||
"messages.markdown_rendering_input_message": "Markdown 渲染输入消息",
|
"messages.markdown_rendering_input_message": "Markdown 渲染输入消息",
|
||||||
"messages.math_engine": "数学公式引擎",
|
"messages.math_engine": "数学公式引擎",
|
||||||
|
"messages.grid_columns": "消息网格展示列数",
|
||||||
|
"messages.grid_popover_trigger": "网格详情触发",
|
||||||
"messages.metrics": "首字时延 {{time_first_token_millsec}}ms | 每秒 {{token_speed}} tokens",
|
"messages.metrics": "首字时延 {{time_first_token_millsec}}ms | 每秒 {{token_speed}} tokens",
|
||||||
"messages.model.title": "模型设置",
|
"messages.model.title": "模型设置",
|
||||||
"messages.title": "消息设置",
|
"messages.title": "消息设置",
|
||||||
|
|||||||
@ -364,6 +364,7 @@
|
|||||||
"message.multi_model_style.fold": "折疊",
|
"message.multi_model_style.fold": "折疊",
|
||||||
"message.multi_model_style.horizontal": "水平",
|
"message.multi_model_style.horizontal": "水平",
|
||||||
"message.multi_model_style.vertical": "垂直",
|
"message.multi_model_style.vertical": "垂直",
|
||||||
|
"message.multi_model_style.grid": "网格",
|
||||||
"message.style": "消息樣式",
|
"message.style": "消息樣式",
|
||||||
"message.style.bubble": "氣泡",
|
"message.style.bubble": "氣泡",
|
||||||
"message.style.plain": "簡潔",
|
"message.style.plain": "簡潔",
|
||||||
@ -635,6 +636,8 @@
|
|||||||
"messages.input.show_estimated_tokens": "顯示預估 Token 數",
|
"messages.input.show_estimated_tokens": "顯示預估 Token 數",
|
||||||
"messages.input.title": "輸入設定",
|
"messages.input.title": "輸入設定",
|
||||||
"messages.math_engine": "Markdown 渲染輸入訊息",
|
"messages.math_engine": "Markdown 渲染輸入訊息",
|
||||||
|
"messages.grid_columns": "消息網格展示列數",
|
||||||
|
"messages.grid_popover_trigger": "網格詳情觸發",
|
||||||
"messages.metrics": "首字時延 {{time_first_token_millsec}}ms | 每秒 {{token_speed}} tokens",
|
"messages.metrics": "首字時延 {{time_first_token_millsec}}ms | 每秒 {{token_speed}} tokens",
|
||||||
"messages.model.title": "模型設定",
|
"messages.model.title": "模型設定",
|
||||||
"messages.title": "訊息設定",
|
"messages.title": "訊息設定",
|
||||||
|
|||||||
@ -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 ModelAvatar from '@renderer/components/Avatar/ModelAvatar'
|
||||||
import { HStack } from '@renderer/components/Layout'
|
import { HStack } from '@renderer/components/Layout'
|
||||||
import Scrollbar from '@renderer/components/Scrollbar'
|
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 { 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 as AntdSegmented } from 'antd'
|
import { Button, Popover, Segmented as AntdSegmented } from 'antd'
|
||||||
import { Dispatch, FC, SetStateAction, useEffect, useState } from 'react'
|
import { Dispatch, FC, SetStateAction, useEffect, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import styled, { css } from 'styled-components'
|
import styled, { css } from 'styled-components'
|
||||||
@ -32,7 +38,7 @@ const MessageGroup: FC<Props> = ({
|
|||||||
onGetMessages,
|
onGetMessages,
|
||||||
onDeleteGroupMessages
|
onDeleteGroupMessages
|
||||||
}) => {
|
}) => {
|
||||||
const { multiModelMessageStyle: multiModelMessageStyleSetting } = useSettings()
|
const { multiModelMessageStyle: multiModelMessageStyleSetting, gridColumns, gridPopoverTrigger } = useSettings()
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const [multiModelMessageStyle, setMultiModelMessageStyle] =
|
const [multiModelMessageStyle, setMultiModelMessageStyle] =
|
||||||
@ -67,8 +73,63 @@ const MessageGroup: FC<Props> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<GroupContainer $isGrouped={isGrouped} $layout={multiModelMessageStyle}>
|
<GroupContainer $isGrouped={isGrouped} $layout={multiModelMessageStyle}>
|
||||||
<GridContainer $count={messageLength} $layout={multiModelMessageStyle}>
|
<GridContainer $count={messageLength} $layout={multiModelMessageStyle} $gridColumns={gridColumns}>
|
||||||
{messages.map((message, index) => (
|
{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
|
<MessageWrapper
|
||||||
$layout={multiModelMessageStyle}
|
$layout={multiModelMessageStyle}
|
||||||
$selected={index === selectedIndex}
|
$selected={index === selectedIndex}
|
||||||
@ -81,19 +142,20 @@ const MessageGroup: FC<Props> = ({
|
|||||||
topic={topic}
|
topic={topic}
|
||||||
index={message.index}
|
index={message.index}
|
||||||
hidePresetMessages={hidePresetMessages}
|
hidePresetMessages={hidePresetMessages}
|
||||||
style={{ paddingTop: isGrouped && multiModelMessageStyle === 'horizontal' ? 0 : 15 }}
|
style={{ paddingTop: isGrouped && ['horizontal', 'grid'].includes(multiModelMessageStyle) ? 0 : 15 }}
|
||||||
onSetMessages={onSetMessages}
|
onSetMessages={onSetMessages}
|
||||||
onDeleteMessage={onDeleteMessage}
|
onDeleteMessage={onDeleteMessage}
|
||||||
onGetMessages={onGetMessages}
|
onGetMessages={onGetMessages}
|
||||||
/>
|
/>
|
||||||
</MessageWrapper>
|
</MessageWrapper>
|
||||||
))}
|
)
|
||||||
|
)}
|
||||||
</GridContainer>
|
</GridContainer>
|
||||||
{isGrouped && (
|
{isGrouped && (
|
||||||
<GroupMenuBar className="group-menu-bar" $layout={multiModelMessageStyle}>
|
<GroupMenuBar className="group-menu-bar" $layout={multiModelMessageStyle}>
|
||||||
<HStack style={{ alignItems: 'center', flex: 1, overflow: 'hidden' }}>
|
<HStack style={{ alignItems: 'center', flex: 1, overflow: 'hidden' }}>
|
||||||
<LayoutContainer>
|
<LayoutContainer>
|
||||||
{['fold', 'vertical', 'horizontal'].map((layout) => (
|
{['fold', 'vertical', 'horizontal', 'grid'].map((layout) => (
|
||||||
<LayoutOption
|
<LayoutOption
|
||||||
key={layout}
|
key={layout}
|
||||||
active={multiModelMessageStyle === layout}
|
active={multiModelMessageStyle === layout}
|
||||||
@ -102,8 +164,10 @@ const MessageGroup: FC<Props> = ({
|
|||||||
<FolderOutlined />
|
<FolderOutlined />
|
||||||
) : layout === 'horizontal' ? (
|
) : layout === 'horizontal' ? (
|
||||||
<ColumnWidthOutlined />
|
<ColumnWidthOutlined />
|
||||||
) : (
|
) : layout === 'vertical' ? (
|
||||||
<ColumnHeightOutlined />
|
<ColumnHeightOutlined />
|
||||||
|
) : (
|
||||||
|
<NumberOutlined />
|
||||||
)}
|
)}
|
||||||
</LayoutOption>
|
</LayoutOption>
|
||||||
))}
|
))}
|
||||||
@ -143,30 +207,38 @@ const MessageGroup: FC<Props> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const GroupContainer = styled.div<{ $isGrouped: boolean; $layout: MultiModelMessageStyle }>`
|
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%;
|
width: 100%;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(
|
grid-template-columns: repeat(
|
||||||
${(props) => (['fold', 'vertical'].includes(props.$layout) ? 1 : props.$count)},
|
${({ $layout, $count }) => (['fold', 'vertical'].includes($layout) ? 1 : $count)},
|
||||||
minmax(550px, 1fr)
|
minmax(550px, 1fr)
|
||||||
);
|
);
|
||||||
gap: ${({ $layout }) => ($layout === 'horizontal' ? '16px' : '0')};
|
gap: ${({ $layout }) => ($layout === 'horizontal' ? '16px' : '0')};
|
||||||
@media (max-width: 800px) {
|
@media (max-width: 800px) {
|
||||||
grid-template-columns: repeat(
|
grid-template-columns: repeat(
|
||||||
${(props) => (['fold', 'vertical'].includes(props.$layout) ? 1 : props.$count)},
|
${({ $layout, $count }) => (['fold', 'vertical'].includes($layout) ? 1 : $count)},
|
||||||
minmax(400px, 1fr)
|
minmax(400px, 1fr)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
overflow-y: auto;
|
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 {
|
interface MessageWrapperProps {
|
||||||
$layout: 'fold' | 'horizontal' | 'vertical'
|
$layout: 'fold' | 'horizontal' | 'vertical' | 'grid'
|
||||||
$selected: boolean
|
$selected: boolean
|
||||||
$isGrouped: boolean
|
$isGrouped: boolean
|
||||||
|
$isInPopover?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const MessageWrapper = styled(Scrollbar)<MessageWrapperProps>`
|
const MessageWrapper = styled(Scrollbar)<MessageWrapperProps>`
|
||||||
@ -193,6 +265,22 @@ const MessageWrapper = styled(Scrollbar)<MessageWrapperProps>`
|
|||||||
}
|
}
|
||||||
return ''
|
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 }>`
|
const GroupMenuBar = styled.div<{ $layout: MultiModelMessageStyle }>`
|
||||||
|
|||||||
@ -60,12 +60,16 @@ const MessageMenubar: FC<Props> = (props) => {
|
|||||||
|
|
||||||
const isUserMessage = message.role === 'user'
|
const isUserMessage = message.role === 'user'
|
||||||
|
|
||||||
const onCopy = useCallback(() => {
|
const onCopy = useCallback(
|
||||||
|
(e: React.MouseEvent) => {
|
||||||
|
e.stopPropagation()
|
||||||
navigator.clipboard.writeText(removeTrailingDoubleSpaces(message.content))
|
navigator.clipboard.writeText(removeTrailingDoubleSpaces(message.content))
|
||||||
window.message.success({ content: t('message.copied'), key: 'copy-message' })
|
window.message.success({ content: t('message.copied'), key: 'copy-message' })
|
||||||
setCopied(true)
|
setCopied(true)
|
||||||
setTimeout(() => setCopied(false), 2000)
|
setTimeout(() => setCopied(false), 2000)
|
||||||
}, [message.content, t])
|
},
|
||||||
|
[message.content, t]
|
||||||
|
)
|
||||||
|
|
||||||
const onNewBranch = useCallback(async () => {
|
const onNewBranch = useCallback(async () => {
|
||||||
await modelGenerating()
|
await modelGenerating()
|
||||||
@ -195,14 +199,16 @@ const MessageMenubar: FC<Props> = (props) => {
|
|||||||
[message, onEdit, onNewBranch, t]
|
[message, onEdit, onNewBranch, t]
|
||||||
)
|
)
|
||||||
|
|
||||||
const onRegenerate = async () => {
|
const onRegenerate = async (e: React.MouseEvent | undefined) => {
|
||||||
|
e?.stopPropagation?.()
|
||||||
await modelGenerating()
|
await modelGenerating()
|
||||||
const selectedModel = isGrouped ? model : assistantModel
|
const selectedModel = isGrouped ? model : assistantModel
|
||||||
const _message = resetAssistantMessage(message, selectedModel)
|
const _message = resetAssistantMessage(message, selectedModel)
|
||||||
onEditMessage?.(_message)
|
onEditMessage?.(_message)
|
||||||
}
|
}
|
||||||
|
|
||||||
const onMentionModel = async () => {
|
const onMentionModel = async (e: React.MouseEvent) => {
|
||||||
|
e.stopPropagation()
|
||||||
await modelGenerating()
|
await modelGenerating()
|
||||||
const selectedModel = await SelectModelPopup.show({ model })
|
const selectedModel = await SelectModelPopup.show({ model })
|
||||||
if (!selectedModel) return
|
if (!selectedModel) return
|
||||||
@ -216,9 +222,13 @@ const MessageMenubar: FC<Props> = (props) => {
|
|||||||
onEditMessage?.(_message)
|
onEditMessage?.(_message)
|
||||||
}
|
}
|
||||||
|
|
||||||
const onUseful = useCallback(() => {
|
const onUseful = useCallback(
|
||||||
|
(e: React.MouseEvent) => {
|
||||||
|
e.stopPropagation()
|
||||||
onEditMessage?.({ ...message, useful: !message.useful })
|
onEditMessage?.({ ...message, useful: !message.useful })
|
||||||
}, [message, onEditMessage])
|
},
|
||||||
|
[message, onEditMessage]
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MenusBar className={`menubar ${isLastMessage && 'show'}`}>
|
<MenusBar className={`menubar ${isLastMessage && 'show'}`}>
|
||||||
@ -270,13 +280,14 @@ const MessageMenubar: FC<Props> = (props) => {
|
|||||||
key: 'translate-close',
|
key: 'translate-close',
|
||||||
onClick: () => onEditMessage?.({ ...message, translatedContent: undefined })
|
onClick: () => onEditMessage?.({ ...message, translatedContent: undefined })
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
onClick: (e) => e.domEvent.stopPropagation()
|
||||||
}}
|
}}
|
||||||
trigger={['click']}
|
trigger={['click']}
|
||||||
placement="topRight"
|
placement="topRight"
|
||||||
arrow>
|
arrow>
|
||||||
<Tooltip title={t('chat.translate')} mouseEnterDelay={1.2}>
|
<Tooltip title={t('chat.translate')} mouseEnterDelay={1.2}>
|
||||||
<ActionButton className="message-action-button">
|
<ActionButton className="message-action-button" onClick={(e) => e.stopPropagation()}>
|
||||||
<TranslationOutlined />
|
<TranslationOutlined />
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@ -298,14 +309,25 @@ const MessageMenubar: FC<Props> = (props) => {
|
|||||||
<Tooltip title={t('common.delete')} mouseEnterDelay={1}>
|
<Tooltip title={t('common.delete')} mouseEnterDelay={1}>
|
||||||
<ActionButton
|
<ActionButton
|
||||||
className="message-action-button"
|
className="message-action-button"
|
||||||
onClick={isGrouped ? () => onDeleteMessage?.(message) : undefined}>
|
onClick={
|
||||||
|
isGrouped
|
||||||
|
? (e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
onDeleteMessage?.(message)
|
||||||
|
}
|
||||||
|
: (e) => e.stopPropagation()
|
||||||
|
}>
|
||||||
<DeleteOutlined />
|
<DeleteOutlined />
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Popconfirm>
|
</Popconfirm>
|
||||||
{!isUserMessage && (
|
{!isUserMessage && (
|
||||||
<Dropdown menu={{ items: dropdownItems }} trigger={['click']} placement="topRight" arrow>
|
<Dropdown
|
||||||
<ActionButton className="message-action-button">
|
menu={{ items: dropdownItems, onClick: (e) => e.domEvent.stopPropagation() }}
|
||||||
|
trigger={['click']}
|
||||||
|
placement="topRight"
|
||||||
|
arrow>
|
||||||
|
<ActionButton className="message-action-button" onClick={(e) => e.stopPropagation()}>
|
||||||
<MenuOutlined />
|
<MenuOutlined />
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
|
|||||||
@ -19,6 +19,8 @@ import {
|
|||||||
setCodeShowLineNumbers,
|
setCodeShowLineNumbers,
|
||||||
setCodeStyle,
|
setCodeStyle,
|
||||||
setFontSize,
|
setFontSize,
|
||||||
|
setGridColumns,
|
||||||
|
setGridPopoverTrigger,
|
||||||
setMathEngine,
|
setMathEngine,
|
||||||
setMessageFont,
|
setMessageFont,
|
||||||
setMessageStyle,
|
setMessageStyle,
|
||||||
@ -42,13 +44,14 @@ interface Props {
|
|||||||
|
|
||||||
const SettingsTab: FC<Props> = (props) => {
|
const SettingsTab: FC<Props> = (props) => {
|
||||||
const { assistant, updateAssistantSettings, updateAssistant } = useAssistant(props.assistant.id)
|
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 [temperature, setTemperature] = useState(assistant?.settings?.temperature ?? DEFAULT_TEMPERATURE)
|
||||||
const [contextCount, setContextCount] = useState(assistant?.settings?.contextCount ?? DEFAULT_CONTEXTCOUNT)
|
const [contextCount, setContextCount] = useState(assistant?.settings?.contextCount ?? DEFAULT_CONTEXTCOUNT)
|
||||||
const [enableMaxTokens, setEnableMaxTokens] = useState(assistant?.settings?.enableMaxTokens ?? false)
|
const [enableMaxTokens, setEnableMaxTokens] = useState(assistant?.settings?.enableMaxTokens ?? false)
|
||||||
const [maxTokens, setMaxTokens] = useState(assistant?.settings?.maxTokens ?? 0)
|
const [maxTokens, setMaxTokens] = useState(assistant?.settings?.maxTokens ?? 0)
|
||||||
const [fontSizeValue, setFontSizeValue] = useState(fontSize)
|
const [fontSizeValue, setFontSizeValue] = useState(fontSize)
|
||||||
|
const [gridColumnsValue, setGridColumnsValue] = useState(gridColumns)
|
||||||
const [streamOutput, setStreamOutput] = useState(assistant?.settings?.streamOutput ?? true)
|
const [streamOutput, setStreamOutput] = useState(assistant?.settings?.streamOutput ?? true)
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
@ -69,7 +72,8 @@ const SettingsTab: FC<Props> = (props) => {
|
|||||||
mathEngine,
|
mathEngine,
|
||||||
autoTranslateWithSpace,
|
autoTranslateWithSpace,
|
||||||
pasteLongTextThreshold,
|
pasteLongTextThreshold,
|
||||||
multiModelMessageStyle
|
multiModelMessageStyle,
|
||||||
|
gridPopoverTrigger
|
||||||
} = useSettings()
|
} = useSettings()
|
||||||
|
|
||||||
const onUpdateAssistantSettings = (settings: Partial<AssistantSettings>) => {
|
const onUpdateAssistantSettings = (settings: Partial<AssistantSettings>) => {
|
||||||
@ -283,6 +287,7 @@ const SettingsTab: FC<Props> = (props) => {
|
|||||||
<Select.Option value="fold">{t('message.message.multi_model_style.fold')}</Select.Option>
|
<Select.Option value="fold">{t('message.message.multi_model_style.fold')}</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="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="grid">{t('message.message.multi_model_style.grid')}</Select.Option>
|
||||||
</Select>
|
</Select>
|
||||||
</SettingRow>
|
</SettingRow>
|
||||||
<SettingDivider />
|
<SettingDivider />
|
||||||
@ -313,6 +318,34 @@ const SettingsTab: FC<Props> = (props) => {
|
|||||||
</Select>
|
</Select>
|
||||||
</SettingRow>
|
</SettingRow>
|
||||||
<SettingDivider />
|
<SettingDivider />
|
||||||
|
<SettingRow>
|
||||||
|
<SettingRowTitleSmall>{t('settings.messages.grid_popover_trigger')}</SettingRowTitleSmall>
|
||||||
|
<Select
|
||||||
|
value={gridPopoverTrigger || 'hover'}
|
||||||
|
onChange={(value) => dispatch(setGridPopoverTrigger(value))}
|
||||||
|
style={{ width: 135 }}
|
||||||
|
size="small">
|
||||||
|
<Select.Option value="hover">hover</Select.Option>
|
||||||
|
<Select.Option value="click">click</Select.Option>
|
||||||
|
</Select>
|
||||||
|
</SettingRow>
|
||||||
|
<SettingDivider />
|
||||||
|
<SettingRow>
|
||||||
|
<SettingRowTitleSmall>{t('settings.messages.grid_columns')}</SettingRowTitleSmall>
|
||||||
|
</SettingRow>
|
||||||
|
<Row align="middle" gutter={10}>
|
||||||
|
<Col span={24}>
|
||||||
|
<Slider
|
||||||
|
value={gridColumnsValue}
|
||||||
|
onChange={(value) => setGridColumnsValue(value)}
|
||||||
|
onChangeComplete={(value) => dispatch(setGridColumns(value))}
|
||||||
|
min={2}
|
||||||
|
max={9}
|
||||||
|
step={1}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<SettingDivider />
|
||||||
<SettingRow>
|
<SettingRow>
|
||||||
<SettingRowTitleSmall>{t('settings.font_size.title')}</SettingRowTitleSmall>
|
<SettingRowTitleSmall>{t('settings.font_size.title')}</SettingRowTitleSmall>
|
||||||
</SettingRow>
|
</SettingRow>
|
||||||
|
|||||||
@ -44,6 +44,8 @@ export interface SettingsState {
|
|||||||
mathEngine: 'MathJax' | 'KaTeX'
|
mathEngine: 'MathJax' | 'KaTeX'
|
||||||
messageStyle: 'plain' | 'bubble'
|
messageStyle: 'plain' | 'bubble'
|
||||||
codeStyle: CodeStyleVarious
|
codeStyle: CodeStyleVarious
|
||||||
|
gridColumns: number
|
||||||
|
gridPopoverTrigger: 'hover' | 'click'
|
||||||
// webdav 配置 host, user, pass, path
|
// webdav 配置 host, user, pass, path
|
||||||
webdavHost: string
|
webdavHost: string
|
||||||
webdavUser: string
|
webdavUser: string
|
||||||
@ -69,7 +71,7 @@ export interface SettingsState {
|
|||||||
notionApiKey: string | null
|
notionApiKey: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export type MultiModelMessageStyle = 'horizontal' | 'vertical' | 'fold'
|
export type MultiModelMessageStyle = 'horizontal' | 'vertical' | 'fold' | 'grid'
|
||||||
|
|
||||||
const initialState: SettingsState = {
|
const initialState: SettingsState = {
|
||||||
showAssistants: true,
|
showAssistants: true,
|
||||||
@ -99,6 +101,8 @@ const initialState: SettingsState = {
|
|||||||
mathEngine: 'KaTeX',
|
mathEngine: 'KaTeX',
|
||||||
messageStyle: 'plain',
|
messageStyle: 'plain',
|
||||||
codeStyle: 'auto',
|
codeStyle: 'auto',
|
||||||
|
gridColumns: 2,
|
||||||
|
gridPopoverTrigger: 'hover',
|
||||||
webdavHost: '',
|
webdavHost: '',
|
||||||
webdavUser: '',
|
webdavUser: '',
|
||||||
webdavPass: '',
|
webdavPass: '',
|
||||||
@ -224,6 +228,12 @@ const settingsSlice = createSlice({
|
|||||||
setMathEngine: (state, action: PayloadAction<'MathJax' | 'KaTeX'>) => {
|
setMathEngine: (state, action: PayloadAction<'MathJax' | 'KaTeX'>) => {
|
||||||
state.mathEngine = action.payload
|
state.mathEngine = action.payload
|
||||||
},
|
},
|
||||||
|
setGridColumns: (state, action: PayloadAction<number>) => {
|
||||||
|
state.gridColumns = action.payload
|
||||||
|
},
|
||||||
|
setGridPopoverTrigger: (state, action: PayloadAction<'hover' | 'click'>) => {
|
||||||
|
state.gridPopoverTrigger = action.payload
|
||||||
|
},
|
||||||
setMessageStyle: (state, action: PayloadAction<'plain' | 'bubble'>) => {
|
setMessageStyle: (state, action: PayloadAction<'plain' | 'bubble'>) => {
|
||||||
state.messageStyle = action.payload
|
state.messageStyle = action.payload
|
||||||
},
|
},
|
||||||
@ -265,7 +275,7 @@ const settingsSlice = createSlice({
|
|||||||
setEnableQuickAssistant: (state, action: PayloadAction<boolean>) => {
|
setEnableQuickAssistant: (state, action: PayloadAction<boolean>) => {
|
||||||
state.enableQuickAssistant = action.payload
|
state.enableQuickAssistant = action.payload
|
||||||
},
|
},
|
||||||
setMultiModelMessageStyle: (state, action: PayloadAction<'horizontal' | 'vertical' | 'fold'>) => {
|
setMultiModelMessageStyle: (state, action: PayloadAction<'horizontal' | 'vertical' | 'fold' | 'grid'>) => {
|
||||||
state.multiModelMessageStyle = action.payload
|
state.multiModelMessageStyle = action.payload
|
||||||
},
|
},
|
||||||
setNotionDatabaseID: (state, action: PayloadAction<string>) => {
|
setNotionDatabaseID: (state, action: PayloadAction<string>) => {
|
||||||
@ -310,6 +320,8 @@ export const {
|
|||||||
setCodeShowLineNumbers,
|
setCodeShowLineNumbers,
|
||||||
setCodeCollapsible,
|
setCodeCollapsible,
|
||||||
setMathEngine,
|
setMathEngine,
|
||||||
|
setGridColumns,
|
||||||
|
setGridPopoverTrigger,
|
||||||
setMessageStyle,
|
setMessageStyle,
|
||||||
setCodeStyle,
|
setCodeStyle,
|
||||||
setTranslateModelPrompt,
|
setTranslateModelPrompt,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user