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