refact: 多模型回答优化
This commit is contained in:
parent
28c18b6651
commit
e72e324155
@ -86,6 +86,7 @@
|
|||||||
"message.new.branch.created": "New Branch Created",
|
"message.new.branch.created": "New Branch Created",
|
||||||
"message.regenerate.model": "Switch Model",
|
"message.regenerate.model": "Switch Model",
|
||||||
"message.new.context": "New Context",
|
"message.new.context": "New Context",
|
||||||
|
"message.useful": "Helpful",
|
||||||
"save": "Save",
|
"save": "Save",
|
||||||
"settings.code_collapsible": "Code block collapsible",
|
"settings.code_collapsible": "Code block collapsible",
|
||||||
"settings.context_count": "Context",
|
"settings.context_count": "Context",
|
||||||
|
|||||||
@ -86,6 +86,7 @@
|
|||||||
"message.new.branch.created": "新しいブランチが作成されました",
|
"message.new.branch.created": "新しいブランチが作成されました",
|
||||||
"message.regenerate.model": "モデルを切り替え",
|
"message.regenerate.model": "モデルを切り替え",
|
||||||
"message.new.context": "新しいコンテキスト",
|
"message.new.context": "新しいコンテキスト",
|
||||||
|
"message.useful": "役立つ",
|
||||||
"save": "保存",
|
"save": "保存",
|
||||||
"settings.code_collapsible": "コードブロックを折りたたむ",
|
"settings.code_collapsible": "コードブロックを折りたたむ",
|
||||||
"settings.context_count": "コンテキスト",
|
"settings.context_count": "コンテキスト",
|
||||||
|
|||||||
@ -86,6 +86,7 @@
|
|||||||
"message.new.branch.created": "Новая ветка создана",
|
"message.new.branch.created": "Новая ветка создана",
|
||||||
"message.regenerate.model": "Переключить модель",
|
"message.regenerate.model": "Переключить модель",
|
||||||
"message.new.context": "Новый контекст",
|
"message.new.context": "Новый контекст",
|
||||||
|
"message.useful": "Полезно",
|
||||||
"save": "Сохранить",
|
"save": "Сохранить",
|
||||||
"settings.code_collapsible": "Блок кода свернут",
|
"settings.code_collapsible": "Блок кода свернут",
|
||||||
"settings.context_count": "Контекст",
|
"settings.context_count": "Контекст",
|
||||||
|
|||||||
@ -86,6 +86,7 @@
|
|||||||
"message.new.branch.created": "新分支已创建",
|
"message.new.branch.created": "新分支已创建",
|
||||||
"message.regenerate.model": "切换模型",
|
"message.regenerate.model": "切换模型",
|
||||||
"message.new.context": "清除上下文",
|
"message.new.context": "清除上下文",
|
||||||
|
"message.useful": "有用",
|
||||||
"save": "保存",
|
"save": "保存",
|
||||||
"settings.code_collapsible": "代码块可折叠",
|
"settings.code_collapsible": "代码块可折叠",
|
||||||
"settings.context_count": "上下文数",
|
"settings.context_count": "上下文数",
|
||||||
@ -253,6 +254,10 @@
|
|||||||
"message.style": "消息样式",
|
"message.style": "消息样式",
|
||||||
"message.style.bubble": "气泡",
|
"message.style.bubble": "气泡",
|
||||||
"message.style.plain": "简洁",
|
"message.style.plain": "简洁",
|
||||||
|
"message.multi_model_style": "多模型回答样式",
|
||||||
|
"message.multi_model_style.horizontal": "水平",
|
||||||
|
"message.multi_model_style.vertical": "垂直",
|
||||||
|
"message.multi_model_style.fold": "折叠",
|
||||||
"reset.confirm.content": "确定要重置所有数据吗?",
|
"reset.confirm.content": "确定要重置所有数据吗?",
|
||||||
"reset.double.confirm.content": "你的全部数据都会丢失,如果没有备份数据,将无法恢复,确定要继续吗?",
|
"reset.double.confirm.content": "你的全部数据都会丢失,如果没有备份数据,将无法恢复,确定要继续吗?",
|
||||||
"reset.double.confirm.title": "数据丢失!!!",
|
"reset.double.confirm.title": "数据丢失!!!",
|
||||||
|
|||||||
@ -86,6 +86,7 @@
|
|||||||
"message.new.branch.created": "新分支已建立",
|
"message.new.branch.created": "新分支已建立",
|
||||||
"message.regenerate.model": "切換模型",
|
"message.regenerate.model": "切換模型",
|
||||||
"message.new.context": "新上下文",
|
"message.new.context": "新上下文",
|
||||||
|
"message.useful": "有用",
|
||||||
"save": "保存",
|
"save": "保存",
|
||||||
"settings.code_collapsible": "代码块可折叠",
|
"settings.code_collapsible": "代码块可折叠",
|
||||||
"settings.context_count": "上下文",
|
"settings.context_count": "上下文",
|
||||||
|
|||||||
@ -139,7 +139,6 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic }) => {
|
|||||||
|
|
||||||
setText('')
|
setText('')
|
||||||
setFiles([])
|
setFiles([])
|
||||||
setMentionModels([])
|
|
||||||
setTimeout(() => setText(''), 500)
|
setTimeout(() => setText(''), 500)
|
||||||
setTimeout(() => resizeTextArea(), 0)
|
setTimeout(() => resizeTextArea(), 0)
|
||||||
|
|
||||||
|
|||||||
@ -194,7 +194,6 @@ const MessageItem: FC<Props> = ({
|
|||||||
const MessageContainer = styled.div`
|
const MessageContainer = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding: 15px 20px 0 20px;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
transition: background-color 0.3s ease;
|
transition: background-color 0.3s ease;
|
||||||
&.message-highlight {
|
&.message-highlight {
|
||||||
|
|||||||
88
src/renderer/src/pages/home/Messages/MessageGroup.tsx
Normal file
88
src/renderer/src/pages/home/Messages/MessageGroup.tsx
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
import { useSettings } from '@renderer/hooks/useSettings'
|
||||||
|
import { Message, Topic } from '@renderer/types'
|
||||||
|
import { Segmented } from 'antd'
|
||||||
|
import { Dispatch, FC, SetStateAction, useState } from 'react'
|
||||||
|
import styled from 'styled-components'
|
||||||
|
|
||||||
|
import MessageItem from './Message'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
messages: (Message & { index: number })[]
|
||||||
|
topic?: Topic
|
||||||
|
hidePresetMessages?: boolean
|
||||||
|
onGetMessages?: () => Message[]
|
||||||
|
onSetMessages?: Dispatch<SetStateAction<Message[]>>
|
||||||
|
onDeleteMessage?: (message: Message) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const MessageGroup: FC<Props> = ({
|
||||||
|
messages,
|
||||||
|
topic,
|
||||||
|
hidePresetMessages,
|
||||||
|
onDeleteMessage,
|
||||||
|
onSetMessages,
|
||||||
|
onGetMessages
|
||||||
|
}) => {
|
||||||
|
const { multiModelMessageStyle } = useSettings()
|
||||||
|
const messageLength = messages.length
|
||||||
|
const [selectedIndex, setSelectedIndex] = useState(0)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<GroupContainer>
|
||||||
|
{messageLength > 1 && multiModelMessageStyle === 'fold' && (
|
||||||
|
<Segmented
|
||||||
|
value={selectedIndex.toString()}
|
||||||
|
onChange={(value) => setSelectedIndex(Number(value))}
|
||||||
|
options={messages.map((message, index) => ({
|
||||||
|
label: `@${message.modelId}`,
|
||||||
|
value: index.toString()
|
||||||
|
}))}
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<GridContainer $count={messageLength} $layout={multiModelMessageStyle}>
|
||||||
|
{messages.map((message, index) => (
|
||||||
|
<MessageWrapper $layout={multiModelMessageStyle} $selected={index === selectedIndex} key={message.id}>
|
||||||
|
<MessageItem
|
||||||
|
message={message}
|
||||||
|
topic={topic}
|
||||||
|
index={message.index}
|
||||||
|
hidePresetMessages={hidePresetMessages}
|
||||||
|
onSetMessages={onSetMessages}
|
||||||
|
onDeleteMessage={onDeleteMessage}
|
||||||
|
onGetMessages={onGetMessages}
|
||||||
|
/>
|
||||||
|
</MessageWrapper>
|
||||||
|
))}
|
||||||
|
</GridContainer>
|
||||||
|
</GroupContainer>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const GroupContainer = styled.div``
|
||||||
|
|
||||||
|
const GridContainer = styled.div<{ $count: number; $layout: 'fold' | 'horizontal' | 'vertical' }>`
|
||||||
|
width: 100%;
|
||||||
|
overflow-x: auto;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(
|
||||||
|
${(props) => (['fold', 'vertical'].includes(props.$layout) ? 1 : props.$count)},
|
||||||
|
minmax(400px, 1fr)
|
||||||
|
);
|
||||||
|
gap: 16px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const MessageWrapper = styled.div<{ $layout: 'fold' | 'horizontal' | 'vertical'; $selected: boolean }>`
|
||||||
|
width: 100%;
|
||||||
|
display: ${(props) => {
|
||||||
|
if (props.$layout === 'fold') {
|
||||||
|
return props.$selected ? 'block' : 'none'
|
||||||
|
}
|
||||||
|
if (props.$layout === 'horizontal') {
|
||||||
|
return 'inline-block'
|
||||||
|
}
|
||||||
|
return 'block'
|
||||||
|
}};
|
||||||
|
`
|
||||||
|
|
||||||
|
export default MessageGroup
|
||||||
@ -3,6 +3,8 @@ import {
|
|||||||
DeleteOutlined,
|
DeleteOutlined,
|
||||||
EditOutlined,
|
EditOutlined,
|
||||||
ForkOutlined,
|
ForkOutlined,
|
||||||
|
LikeFilled,
|
||||||
|
LikeOutlined,
|
||||||
MenuOutlined,
|
MenuOutlined,
|
||||||
QuestionCircleOutlined,
|
QuestionCircleOutlined,
|
||||||
SaveOutlined,
|
SaveOutlined,
|
||||||
@ -43,7 +45,6 @@ const MessageMenubar: FC<Props> = (props) => {
|
|||||||
isLastMessage,
|
isLastMessage,
|
||||||
isAssistantMessage,
|
isAssistantMessage,
|
||||||
assistantModel,
|
assistantModel,
|
||||||
setModel,
|
|
||||||
onEditMessage,
|
onEditMessage,
|
||||||
onDeleteMessage,
|
onDeleteMessage,
|
||||||
onGetMessages
|
onGetMessages
|
||||||
@ -53,7 +54,6 @@ const MessageMenubar: FC<Props> = (props) => {
|
|||||||
const [isTranslating, setIsTranslating] = useState(false)
|
const [isTranslating, setIsTranslating] = useState(false)
|
||||||
|
|
||||||
const isUserMessage = message.role === 'user'
|
const isUserMessage = message.role === 'user'
|
||||||
const canRegenerate = isLastMessage && isAssistantMessage
|
|
||||||
|
|
||||||
const onCopy = useCallback(() => {
|
const onCopy = useCallback(() => {
|
||||||
navigator.clipboard.writeText(removeTrailingDoubleSpaces(message.content))
|
navigator.clipboard.writeText(removeTrailingDoubleSpaces(message.content))
|
||||||
@ -62,14 +62,6 @@ const MessageMenubar: FC<Props> = (props) => {
|
|||||||
setTimeout(() => setCopied(false), 2000)
|
setTimeout(() => setCopied(false), 2000)
|
||||||
}, [message.content, t])
|
}, [message.content, t])
|
||||||
|
|
||||||
const onRegenerate = useCallback(
|
|
||||||
(model: Model) => {
|
|
||||||
setModel(model)
|
|
||||||
setTimeout(() => EventEmitter.emit(EVENT_NAMES.REGENERATE_MESSAGE, model), 100)
|
|
||||||
},
|
|
||||||
[setModel]
|
|
||||||
)
|
|
||||||
|
|
||||||
const onNewBranch = useCallback(async () => {
|
const onNewBranch = useCallback(async () => {
|
||||||
await modelGenerating()
|
await modelGenerating()
|
||||||
EventEmitter.emit(EVENT_NAMES.NEW_BRANCH, index)
|
EventEmitter.emit(EVENT_NAMES.NEW_BRANCH, index)
|
||||||
@ -175,25 +167,27 @@ const MessageMenubar: FC<Props> = (props) => {
|
|||||||
[message, onEdit, onNewBranch, t]
|
[message, onEdit, onNewBranch, t]
|
||||||
)
|
)
|
||||||
|
|
||||||
const onAtModelRegenerate = async () => {
|
|
||||||
await modelGenerating()
|
|
||||||
const selectedModel = await SelectModelPopup.show({ model })
|
|
||||||
selectedModel && onRegenerate(selectedModel)
|
|
||||||
}
|
|
||||||
|
|
||||||
const onDeleteAndRegenerate = async () => {
|
const onDeleteAndRegenerate = async () => {
|
||||||
await modelGenerating()
|
await modelGenerating()
|
||||||
|
const selectedModel = await SelectModelPopup.show({ model })
|
||||||
|
if (!selectedModel) return
|
||||||
|
|
||||||
onEditMessage?.({
|
onEditMessage?.({
|
||||||
...message,
|
...message,
|
||||||
content: '',
|
content: '',
|
||||||
reasoning_content: undefined,
|
reasoning_content: undefined,
|
||||||
metrics: undefined,
|
metrics: undefined,
|
||||||
status: 'sending',
|
status: 'sending',
|
||||||
modelId: assistantModel?.id || model?.id,
|
modelId: selectedModel.id || assistantModel?.id || model?.id,
|
||||||
|
model: selectedModel,
|
||||||
translatedContent: undefined
|
translatedContent: undefined
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const onUseful = useCallback(() => {
|
||||||
|
onEditMessage?.({ ...message, useful: !message.useful })
|
||||||
|
}, [message, onEditMessage])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MenusBar className={`menubar ${isLastMessage && 'show'}`}>
|
<MenusBar className={`menubar ${isLastMessage && 'show'}`}>
|
||||||
{message.role === 'user' && (
|
{message.role === 'user' && (
|
||||||
@ -210,25 +204,11 @@ const MessageMenubar: FC<Props> = (props) => {
|
|||||||
</ActionButton>
|
</ActionButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
{isAssistantMessage && (
|
{isAssistantMessage && (
|
||||||
<Popconfirm
|
|
||||||
title={t('message.regenerate.confirm')}
|
|
||||||
okButtonProps={{ danger: true }}
|
|
||||||
destroyTooltipOnHide
|
|
||||||
icon={<QuestionCircleOutlined style={{ color: 'red' }} />}
|
|
||||||
onConfirm={onDeleteAndRegenerate}>
|
|
||||||
<Tooltip title={t('common.regenerate')} mouseEnterDelay={0.8}>
|
<Tooltip title={t('common.regenerate')} mouseEnterDelay={0.8}>
|
||||||
<ActionButton className="message-action-button">
|
<ActionButton className="message-action-button" onClick={onDeleteAndRegenerate}>
|
||||||
<SyncOutlined />
|
<SyncOutlined />
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Popconfirm>
|
|
||||||
)}
|
|
||||||
{canRegenerate && (
|
|
||||||
<Tooltip title={t('chat.message.regenerate.model')} mouseEnterDelay={0.8}>
|
|
||||||
<ActionButton className="message-action-button" onClick={onAtModelRegenerate}>
|
|
||||||
<i className="iconfont icon-at"></i>
|
|
||||||
</ActionButton>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
)}
|
||||||
{!isUserMessage && (
|
{!isUserMessage && (
|
||||||
<Dropdown
|
<Dropdown
|
||||||
@ -281,6 +261,14 @@ const MessageMenubar: FC<Props> = (props) => {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
)}
|
)}
|
||||||
|
{isAssistantMessage && (
|
||||||
|
<Tooltip title={t('chat.message.useful')} mouseEnterDelay={0.8}>
|
||||||
|
<ActionButton className="message-action-button" onClick={onUseful}>
|
||||||
|
{message.useful ? <LikeFilled /> : <LikeOutlined />}
|
||||||
|
</ActionButton>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
|
||||||
<Popconfirm
|
<Popconfirm
|
||||||
title={t('message.message.delete.content')}
|
title={t('message.message.delete.content')}
|
||||||
okButtonProps={{ danger: true }}
|
okButtonProps={{ danger: true }}
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import {
|
|||||||
filterMessages,
|
filterMessages,
|
||||||
getAssistantMessage,
|
getAssistantMessage,
|
||||||
getContextCount,
|
getContextCount,
|
||||||
|
getGroupedMessages,
|
||||||
getUserMessage
|
getUserMessage
|
||||||
} from '@renderer/services/MessagesService'
|
} from '@renderer/services/MessagesService'
|
||||||
import { estimateHistoryTokens } from '@renderer/services/TokenService'
|
import { estimateHistoryTokens } from '@renderer/services/TokenService'
|
||||||
@ -25,7 +26,7 @@ import BeatLoader from 'react-spinners/BeatLoader'
|
|||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
|
|
||||||
import Suggestions from '../components/Suggestions'
|
import Suggestions from '../components/Suggestions'
|
||||||
import MessageItem from './Message'
|
import MessageGroup from './MessageGroup'
|
||||||
import NarrowLayout from './NarrowLayout'
|
import NarrowLayout from './NarrowLayout'
|
||||||
import Prompt from './Prompt'
|
import Prompt from './Prompt'
|
||||||
|
|
||||||
@ -53,10 +54,12 @@ const LoaderContainer = styled.div<LoaderProps>`
|
|||||||
const ScrollContainer = styled.div`
|
const ScrollContainer = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column-reverse;
|
flex-direction: column-reverse;
|
||||||
|
padding: 16px 16px;
|
||||||
|
gap: 32px;
|
||||||
`
|
`
|
||||||
|
|
||||||
interface ContainerProps {
|
interface ContainerProps {
|
||||||
right?: boolean
|
$right?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const Container = styled(Scrollbar)<ContainerProps>`
|
const Container = styled(Scrollbar)<ContainerProps>`
|
||||||
@ -79,6 +82,8 @@ const Messages: FC<Props> = ({ assistant, topic, setActiveTopic }) => {
|
|||||||
const { updateTopic, addTopic } = useAssistant(assistant.id)
|
const { updateTopic, addTopic } = useAssistant(assistant.id)
|
||||||
const { showTopics, topicPosition, showAssistants, enableTopicNaming } = useSettings()
|
const { showTopics, topicPosition, showAssistants, enableTopicNaming } = useSettings()
|
||||||
|
|
||||||
|
const groupedMessages = getGroupedMessages(displayMessages)
|
||||||
|
|
||||||
const INITIAL_MESSAGES_COUNT = 20
|
const INITIAL_MESSAGES_COUNT = 20
|
||||||
const LOAD_MORE_COUNT = 20
|
const LOAD_MORE_COUNT = 20
|
||||||
|
|
||||||
@ -102,10 +107,13 @@ const Messages: FC<Props> = ({ assistant, topic, setActiveTopic }) => {
|
|||||||
message.mentions.forEach((m) => {
|
message.mentions.forEach((m) => {
|
||||||
const assistantMessage = getAssistantMessage({ assistant: { ...assistant, model: m }, topic })
|
const assistantMessage = getAssistantMessage({ assistant: { ...assistant, model: m }, topic })
|
||||||
assistantMessage.model = m
|
assistantMessage.model = m
|
||||||
|
assistantMessage.askId = message.id
|
||||||
assistantMessages.push(assistantMessage)
|
assistantMessages.push(assistantMessage)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
assistantMessages.push(getAssistantMessage({ assistant, topic }))
|
const assistantMessage = getAssistantMessage({ assistant, topic })
|
||||||
|
assistantMessage.askId = message.id
|
||||||
|
assistantMessages.push(assistantMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
setMessages((prev) => {
|
setMessages((prev) => {
|
||||||
@ -214,7 +222,7 @@ const Messages: FC<Props> = ({ assistant, topic, setActiveTopic }) => {
|
|||||||
setActiveTopic(newTopic)
|
setActiveTopic(newTopic)
|
||||||
autoRenameTopic()
|
autoRenameTopic()
|
||||||
|
|
||||||
// 由于复制了消<EFBFBD><EFBFBD><EFBFBD>,消息中附带的文件的总数变了,需要更新
|
// 由于复制了消息,消息中附带的文件的总数变了,需要更新
|
||||||
const filesArr = branchMessages.map((m) => m.files)
|
const filesArr = branchMessages.map((m) => m.files)
|
||||||
const files = flatten(filesArr).filter(Boolean)
|
const files = flatten(filesArr).filter(Boolean)
|
||||||
files.map(async (f) => {
|
files.map(async (f) => {
|
||||||
@ -293,7 +301,7 @@ const Messages: FC<Props> = ({ assistant, topic, setActiveTopic }) => {
|
|||||||
style={{ maxWidth }}
|
style={{ maxWidth }}
|
||||||
key={assistant.id}
|
key={assistant.id}
|
||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
right={topicPosition === 'left'}>
|
$right={topicPosition === 'left'}>
|
||||||
<NarrowLayout style={{ display: 'flex', flexDirection: 'column-reverse' }}>
|
<NarrowLayout style={{ display: 'flex', flexDirection: 'column-reverse' }}>
|
||||||
<Suggestions assistant={assistant} messages={messages} />
|
<Suggestions assistant={assistant} messages={messages} />
|
||||||
<InfiniteScroll
|
<InfiniteScroll
|
||||||
@ -307,12 +315,11 @@ const Messages: FC<Props> = ({ assistant, topic, setActiveTopic }) => {
|
|||||||
<LoaderContainer $loading={isLoadingMore}>
|
<LoaderContainer $loading={isLoadingMore}>
|
||||||
<BeatLoader size={8} color="var(--color-text-2)" />
|
<BeatLoader size={8} color="var(--color-text-2)" />
|
||||||
</LoaderContainer>
|
</LoaderContainer>
|
||||||
{displayMessages.map((message, index) => (
|
{Object.entries(groupedMessages).map(([key, messages]) => (
|
||||||
<MessageItem
|
<MessageGroup
|
||||||
key={message.id}
|
key={key}
|
||||||
message={message}
|
messages={messages}
|
||||||
topic={topic}
|
topic={topic}
|
||||||
index={index}
|
|
||||||
hidePresetMessages={assistant.settings?.hideMessages}
|
hidePresetMessages={assistant.settings?.hideMessages}
|
||||||
onSetMessages={setMessages}
|
onSetMessages={setMessages}
|
||||||
onDeleteMessage={onDeleteMessage}
|
onDeleteMessage={onDeleteMessage}
|
||||||
|
|||||||
@ -22,6 +22,7 @@ import {
|
|||||||
setMathEngine,
|
setMathEngine,
|
||||||
setMessageFont,
|
setMessageFont,
|
||||||
setMessageStyle,
|
setMessageStyle,
|
||||||
|
setMultiModelMessageStyle,
|
||||||
setPasteLongTextAsFile,
|
setPasteLongTextAsFile,
|
||||||
setPasteLongTextThreshold,
|
setPasteLongTextThreshold,
|
||||||
setRenderInputMessageAsMarkdown,
|
setRenderInputMessageAsMarkdown,
|
||||||
@ -64,7 +65,8 @@ const SettingsTab: FC<Props> = (props) => {
|
|||||||
codeCollapsible,
|
codeCollapsible,
|
||||||
mathEngine,
|
mathEngine,
|
||||||
autoTranslateWithSpace,
|
autoTranslateWithSpace,
|
||||||
pasteLongTextThreshold
|
pasteLongTextThreshold,
|
||||||
|
multiModelMessageStyle
|
||||||
} = useSettings()
|
} = useSettings()
|
||||||
|
|
||||||
const onUpdateAssistantSettings = (settings: Partial<AssistantSettings>) => {
|
const onUpdateAssistantSettings = (settings: Partial<AssistantSettings>) => {
|
||||||
@ -255,6 +257,19 @@ const SettingsTab: FC<Props> = (props) => {
|
|||||||
</Select>
|
</Select>
|
||||||
</SettingRow>
|
</SettingRow>
|
||||||
<SettingDivider />
|
<SettingDivider />
|
||||||
|
<SettingRow>
|
||||||
|
<SettingRowTitleSmall>{t('message.message.multi_model_style')}</SettingRowTitleSmall>
|
||||||
|
<Select
|
||||||
|
size="small"
|
||||||
|
value={multiModelMessageStyle}
|
||||||
|
onChange={(value) => dispatch(setMultiModelMessageStyle(value))}
|
||||||
|
style={{ width: 135 }}>
|
||||||
|
<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 />
|
||||||
<SettingRow>
|
<SettingRow>
|
||||||
<SettingRowTitleSmall>{t('message.message.code_style')}</SettingRowTitleSmall>
|
<SettingRowTitleSmall>{t('message.message.code_style')}</SettingRowTitleSmall>
|
||||||
<Select
|
<Select
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import {
|
|||||||
getTranslateModel
|
getTranslateModel
|
||||||
} from './AssistantService'
|
} from './AssistantService'
|
||||||
import { EVENT_NAMES, EventEmitter } from './EventService'
|
import { EVENT_NAMES, EventEmitter } from './EventService'
|
||||||
import { filterMessages } from './MessagesService'
|
import { filterMessages, filterUsefulMessages } from './MessagesService'
|
||||||
import { estimateMessagesUsage } from './TokenService'
|
import { estimateMessagesUsage } from './TokenService'
|
||||||
|
|
||||||
export async function fetchChatCompletion({
|
export async function fetchChatCompletion({
|
||||||
@ -53,7 +53,7 @@ export async function fetchChatCompletion({
|
|||||||
let _messages: Message[] = []
|
let _messages: Message[] = []
|
||||||
|
|
||||||
await AI.completions({
|
await AI.completions({
|
||||||
messages,
|
messages: filterUsefulMessages(messages),
|
||||||
assistant,
|
assistant,
|
||||||
onFilterMessages: (messages) => (_messages = messages),
|
onFilterMessages: (messages) => (_messages = messages),
|
||||||
onChunk: ({ text, reasoning_content, usage, metrics, search }) => {
|
onChunk: ({ text, reasoning_content, usage, metrics, search }) => {
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import i18n from '@renderer/i18n'
|
|||||||
import store from '@renderer/store'
|
import store from '@renderer/store'
|
||||||
import { Assistant, Message, Topic } from '@renderer/types'
|
import { Assistant, Message, Topic } from '@renderer/types'
|
||||||
import { uuid } from '@renderer/utils'
|
import { uuid } from '@renderer/utils'
|
||||||
import { isEmpty, takeRight } from 'lodash'
|
import { isEmpty, remove, takeRight } from 'lodash'
|
||||||
import { NavigateFunction } from 'react-router'
|
import { NavigateFunction } from 'react-router'
|
||||||
|
|
||||||
import { getAssistantById, getDefaultModel } from './AssistantService'
|
import { getAssistantById, getDefaultModel } from './AssistantService'
|
||||||
@ -109,3 +109,43 @@ export function getAssistantMessage({ assistant, topic }: { assistant: Assistant
|
|||||||
status: 'sending'
|
status: 'sending'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function filterUsefulMessages(messages: Message[]): Message[] {
|
||||||
|
const _messages = messages
|
||||||
|
const groupedMessages = getGroupedMessages(messages)
|
||||||
|
|
||||||
|
Object.entries(groupedMessages).forEach(([key, messages]) => {
|
||||||
|
if (key.startsWith('assistant')) {
|
||||||
|
const usefulMessage = messages.find((m) => m.useful === true)
|
||||||
|
if (usefulMessage) {
|
||||||
|
messages.forEach((m) => {
|
||||||
|
if (m.id !== usefulMessage.id) {
|
||||||
|
remove(_messages, (o) => o.id === m.id)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
messages?.slice(0, -1).forEach((m) => {
|
||||||
|
remove(_messages, (o) => o.id === m.id)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
while (_messages.length > 0 && _messages[_messages.length - 1].role === 'assistant') {
|
||||||
|
_messages.pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
return _messages
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getGroupedMessages(messages: Message[]): { [key: string]: (Message & { index: number })[] } {
|
||||||
|
const groups: { [key: string]: (Message & { index: number })[] } = {}
|
||||||
|
messages.forEach((message, index) => {
|
||||||
|
const key = message.askId ? 'assistant' + message.askId : 'user' + message.id
|
||||||
|
if (key && !groups[key]) {
|
||||||
|
groups[key] = []
|
||||||
|
}
|
||||||
|
groups[key].unshift({ ...message, index })
|
||||||
|
})
|
||||||
|
return groups
|
||||||
|
}
|
||||||
|
|||||||
@ -63,6 +63,7 @@ export interface SettingsState {
|
|||||||
narrowMode: boolean
|
narrowMode: boolean
|
||||||
enableQuickAssistant: boolean
|
enableQuickAssistant: boolean
|
||||||
clickTrayToShowQuickAssistant: boolean
|
clickTrayToShowQuickAssistant: boolean
|
||||||
|
multiModelMessageStyle: 'horizontal' | 'vertical' | 'fold'
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState: SettingsState = {
|
const initialState: SettingsState = {
|
||||||
@ -109,7 +110,8 @@ const initialState: SettingsState = {
|
|||||||
},
|
},
|
||||||
narrowMode: false,
|
narrowMode: false,
|
||||||
enableQuickAssistant: false,
|
enableQuickAssistant: false,
|
||||||
clickTrayToShowQuickAssistant: false
|
clickTrayToShowQuickAssistant: false,
|
||||||
|
multiModelMessageStyle: 'vertical'
|
||||||
}
|
}
|
||||||
|
|
||||||
const settingsSlice = createSlice({
|
const settingsSlice = createSlice({
|
||||||
@ -251,6 +253,9 @@ 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'>) => {
|
||||||
|
state.multiModelMessageStyle = action.payload
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -298,7 +303,8 @@ export const {
|
|||||||
setSidebarIcons,
|
setSidebarIcons,
|
||||||
setNarrowMode,
|
setNarrowMode,
|
||||||
setClickTrayToShowQuickAssistant,
|
setClickTrayToShowQuickAssistant,
|
||||||
setEnableQuickAssistant
|
setEnableQuickAssistant,
|
||||||
|
setMultiModelMessageStyle
|
||||||
} = settingsSlice.actions
|
} = settingsSlice.actions
|
||||||
|
|
||||||
export default settingsSlice.reducer
|
export default settingsSlice.reducer
|
||||||
|
|||||||
@ -66,6 +66,8 @@ export type Message = {
|
|||||||
// Gemini
|
// Gemini
|
||||||
groundingMetadata?: any
|
groundingMetadata?: any
|
||||||
}
|
}
|
||||||
|
askId?: string
|
||||||
|
useful?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Metrics = {
|
export type Metrics = {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user