feat: change re-generage message logic
This commit is contained in:
parent
368de84440
commit
73973ecb7f
@ -1,6 +1,6 @@
|
||||
@font-face {
|
||||
font-family: 'iconfont'; /* Project id 4563475 */
|
||||
src: url('iconfont.woff2?t=1725606177995') format('woff2');
|
||||
font-family: 'iconfont'; /* Project id 4753420 */
|
||||
src: url('iconfont.woff2?t=1733224456443') format('woff2');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
@ -11,6 +11,14 @@
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icon-at1:before {
|
||||
content: '\e7df';
|
||||
}
|
||||
|
||||
.icon-at:before {
|
||||
content: '\e630';
|
||||
}
|
||||
|
||||
.icon-a-darkmode:before {
|
||||
content: '\e6cd';
|
||||
}
|
||||
@ -27,10 +35,6 @@
|
||||
content: '\e942';
|
||||
}
|
||||
|
||||
.icon-grid-row-2copy:before {
|
||||
content: '\e681';
|
||||
}
|
||||
|
||||
.icon-inbox:before {
|
||||
content: '\e869';
|
||||
}
|
||||
@ -71,10 +75,6 @@
|
||||
content: '\e944';
|
||||
}
|
||||
|
||||
.icon-a-addchat:before {
|
||||
content: '\e658';
|
||||
}
|
||||
|
||||
.icon-appstore:before {
|
||||
content: '\e792';
|
||||
}
|
||||
|
||||
Binary file not shown.
@ -49,6 +49,8 @@ const PopupContainer: React.FC<Props> = ({ text, textareaProps, modalProps, reso
|
||||
setTimeout(resizeTextArea, 0)
|
||||
}, [])
|
||||
|
||||
TextEditPopup.hide = onCancel
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={t('common.edit')}
|
||||
@ -75,10 +77,12 @@ const PopupContainer: React.FC<Props> = ({ text, textareaProps, modalProps, reso
|
||||
)
|
||||
}
|
||||
|
||||
const TopViewKey = 'TextEditPopup'
|
||||
|
||||
export default class TextEditPopup {
|
||||
static topviewId = 0
|
||||
static hide() {
|
||||
TopView.hide('TextEditPopup')
|
||||
TopView.hide(TopViewKey)
|
||||
}
|
||||
static show(props: ShowParams) {
|
||||
return new Promise<any>((resolve) => {
|
||||
@ -87,10 +91,10 @@ export default class TextEditPopup {
|
||||
{...props}
|
||||
resolve={(v) => {
|
||||
resolve(v)
|
||||
this.hide()
|
||||
TopView.hide(TopViewKey)
|
||||
}}
|
||||
/>,
|
||||
'TextEditPopup'
|
||||
TopViewKey
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@ -240,7 +240,8 @@
|
||||
"topic.added": "New topic added",
|
||||
"upgrade.success.button": "Restart",
|
||||
"upgrade.success.content": "Please restart the application to complete the upgrade",
|
||||
"upgrade.success.title": "Upgrade successfully"
|
||||
"upgrade.success.title": "Upgrade successfully",
|
||||
"regenerate.confirm": "Regenerating will replace current message"
|
||||
},
|
||||
"minapp": {
|
||||
"title": "MinApp"
|
||||
|
||||
@ -240,7 +240,8 @@
|
||||
"topic.added": "Новый топик добавлен",
|
||||
"upgrade.success.button": "Перезапустить",
|
||||
"upgrade.success.content": "Пожалуйста, перезапустите приложение для завершения обновления",
|
||||
"upgrade.success.title": "Обновление успешно"
|
||||
"upgrade.success.title": "Обновление успешно",
|
||||
"regenerate.confirm": "Перегенерация заменит текущее сообщение"
|
||||
},
|
||||
"minapp": {
|
||||
"title": "Встроенные приложения"
|
||||
|
||||
@ -240,7 +240,8 @@
|
||||
"topic.added": "话题添加成功",
|
||||
"upgrade.success.button": "重启",
|
||||
"upgrade.success.content": "重启用以完成升级",
|
||||
"upgrade.success.title": "升级成功"
|
||||
"upgrade.success.title": "升级成功",
|
||||
"regenerate.confirm": "重新生成会覆盖当前消息"
|
||||
},
|
||||
"minapp": {
|
||||
"title": "小程序"
|
||||
|
||||
@ -240,7 +240,8 @@
|
||||
"topic.added": "新話題已添加",
|
||||
"upgrade.success.button": "重新啟動",
|
||||
"upgrade.success.content": "請重新啟動應用以完成升級",
|
||||
"upgrade.success.title": "升級成功"
|
||||
"upgrade.success.title": "升級成功",
|
||||
"regenerate.confirm": "重新生成會覆蓋當前訊息"
|
||||
},
|
||||
"minapp": {
|
||||
"title": "小程序"
|
||||
|
||||
@ -272,8 +272,8 @@ const Tabs = styled(TabsAntd)<{ $language: string }>`
|
||||
padding-right: 0 !important;
|
||||
}
|
||||
.ant-tabs-nav {
|
||||
min-width: ${({ $language }) => ($language.startsWith('zh') ? '100px' : '140px')};
|
||||
max-width: ${({ $language }) => ($language.startsWith('zh') ? '100px' : '140px')};
|
||||
min-width: ${({ $language }) => ($language.startsWith('zh') ? '110px' : '140px')};
|
||||
max-width: ${({ $language }) => ($language.startsWith('zh') ? '110px' : '140px')};
|
||||
}
|
||||
.ant-tabs-nav-list {
|
||||
padding: 10px 8px;
|
||||
|
||||
@ -30,6 +30,9 @@ interface Props {
|
||||
onDeleteMessage?: (message: Message) => void
|
||||
}
|
||||
|
||||
const getMessageBackground = (isBubbleStyle: boolean, isAssistantMessage: boolean) =>
|
||||
isBubbleStyle ? (isAssistantMessage ? 'var(--chat-background-assistant)' : 'var(--chat-background-user)') : undefined
|
||||
|
||||
const MessageItem: FC<Props> = ({
|
||||
message: _message,
|
||||
topic,
|
||||
@ -57,25 +60,19 @@ const MessageItem: FC<Props> = ({
|
||||
}, [messageFont])
|
||||
|
||||
const messageBorder = showMessageDivider ? undefined : 'none'
|
||||
const messageBackground = isBubbleStyle
|
||||
? isAssistantMessage
|
||||
? 'var(--chat-background-assistant)'
|
||||
: 'var(--chat-background-user)'
|
||||
: undefined
|
||||
const messageBackground = getMessageBackground(isBubbleStyle, isAssistantMessage)
|
||||
|
||||
const onEditMessage = useCallback(
|
||||
(msg: Message) => {
|
||||
setMessage(msg)
|
||||
const messages = onGetMessages?.().map((m) => (m.id === message.id ? msg : m))
|
||||
const messages = onGetMessages?.()?.map((m) => (m.id === message.id ? msg : m))
|
||||
messages && onSetMessages?.(messages)
|
||||
topic && db.topics.update(topic.id, { messages })
|
||||
},
|
||||
[message, onGetMessages, onSetMessages, topic]
|
||||
[message.id, onGetMessages, onSetMessages, topic]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
const unsubscribes = [
|
||||
EventEmitter.on(EVENT_NAMES.LOCATE_MESSAGE + ':' + message.id, (highlight: boolean = true) => {
|
||||
const messageHighlightHandler = (highlight: boolean = true) => {
|
||||
if (messageContainerRef.current) {
|
||||
messageContainerRef.current.scrollIntoView({ behavior: 'smooth' })
|
||||
if (highlight) {
|
||||
@ -86,8 +83,10 @@ const MessageItem: FC<Props> = ({
|
||||
}, 500)
|
||||
}
|
||||
}
|
||||
})
|
||||
]
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const unsubscribes = [EventEmitter.on(EVENT_NAMES.LOCATE_MESSAGE + ':' + message.id, messageHighlightHandler)]
|
||||
return () => unsubscribes.forEach((unsub) => unsub())
|
||||
}, [message])
|
||||
|
||||
@ -105,11 +104,16 @@ const MessageItem: FC<Props> = ({
|
||||
|
||||
useEffect(() => {
|
||||
if (topic && onGetMessages && onSetMessages) {
|
||||
if (message.status === 'sending' && index === 0) {
|
||||
if (message.status === 'sending') {
|
||||
const messages = onGetMessages()
|
||||
fetchChatCompletion({
|
||||
message,
|
||||
messages: messages.filter((m) => !m.status.includes('ing')),
|
||||
messages: messages
|
||||
.filter((m) => !m.status.includes('ing'))
|
||||
.slice(
|
||||
0,
|
||||
messages.findIndex((m) => m.id === message.id)
|
||||
),
|
||||
assistant,
|
||||
topic,
|
||||
onResponse: (msg) => {
|
||||
@ -124,7 +128,7 @@ const MessageItem: FC<Props> = ({
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
}, [message.status])
|
||||
|
||||
if (hidePresetMessages && message.isPreset) {
|
||||
return null
|
||||
@ -148,7 +152,7 @@ const MessageItem: FC<Props> = ({
|
||||
})}
|
||||
ref={messageContainerRef}
|
||||
style={isBubbleStyle ? { alignItems: isAssistantMessage ? 'start' : 'end' } : undefined}>
|
||||
<MessageHeader message={message} assistant={assistant} model={model} />
|
||||
<MessageHeader message={message} assistant={assistant} model={model} key={message.modelId} />
|
||||
<MessageContentContainer
|
||||
className="message-content-container"
|
||||
style={{ fontFamily, fontSize, background: messageBackground }}>
|
||||
@ -164,6 +168,7 @@ const MessageItem: FC<Props> = ({
|
||||
<MessageTokens message={message} isLastMessage={isLastMessage} />
|
||||
<MessageMenubar
|
||||
message={message}
|
||||
assistantModel={assistant.model}
|
||||
model={model}
|
||||
index={index}
|
||||
isLastMessage={isLastMessage}
|
||||
|
||||
@ -9,7 +9,7 @@ import { Assistant, Message, Model } from '@renderer/types'
|
||||
import { firstLetter, removeLeadingEmoji } from '@renderer/utils'
|
||||
import { Avatar } from 'antd'
|
||||
import dayjs from 'dayjs'
|
||||
import { CSSProperties, FC, useCallback, useMemo } from 'react'
|
||||
import { CSSProperties, FC, memo, useCallback, useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
|
||||
@ -19,18 +19,19 @@ interface Props {
|
||||
model?: Model
|
||||
}
|
||||
|
||||
const MessageHeader: FC<Props> = ({ assistant, model, message }) => {
|
||||
const getAvatarSource = (isLocalAi: boolean, modelId: string | undefined) => {
|
||||
if (isLocalAi) return AppLogo
|
||||
return modelId ? getModelLogo(modelId) : undefined
|
||||
}
|
||||
|
||||
const MessageHeader: FC<Props> = memo(({ assistant, model, message }) => {
|
||||
const avatar = useAvatar()
|
||||
const { theme } = useTheme()
|
||||
const { userName } = useSettings()
|
||||
const { t } = useTranslation()
|
||||
const { isBubbleStyle } = useMessageStyle()
|
||||
|
||||
const avatarSource = useMemo(() => {
|
||||
if (isLocalAi) return AppLogo
|
||||
return message.modelId ? getModelLogo(message.modelId) : undefined
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [message.modelId, theme])
|
||||
const avatarSource = useMemo(() => getAvatarSource(isLocalAi, message.modelId), [message.modelId])
|
||||
|
||||
const getUserName = useCallback(() => {
|
||||
if (isLocalAi && message.role !== 'user') return APP_NAME
|
||||
@ -43,7 +44,7 @@ const MessageHeader: FC<Props> = ({ assistant, model, message }) => {
|
||||
const avatarName = useMemo(() => firstLetter(assistant?.name).toUpperCase(), [assistant?.name])
|
||||
const username = useMemo(() => removeLeadingEmoji(getUserName()), [getUserName])
|
||||
|
||||
const showMiniApp = () => model?.provider && startMinAppById(model?.provider)
|
||||
const showMiniApp = useCallback(() => model?.provider && startMinAppById(model.provider), [model?.provider])
|
||||
|
||||
const avatarStyle: CSSProperties | undefined = isBubbleStyle
|
||||
? {
|
||||
@ -83,7 +84,9 @@ const MessageHeader: FC<Props> = ({ assistant, model, message }) => {
|
||||
</AvatarWrapper>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
MessageHeader.displayName = 'MessageHeader'
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
|
||||
@ -23,6 +23,7 @@ import styled from 'styled-components'
|
||||
|
||||
interface Props {
|
||||
message: Message
|
||||
assistantModel?: Model
|
||||
model?: Model
|
||||
index?: number
|
||||
isLastMessage: boolean
|
||||
@ -33,7 +34,17 @@ interface Props {
|
||||
}
|
||||
|
||||
const MessageMenubar: FC<Props> = (props) => {
|
||||
const { message, index, model, isLastMessage, isAssistantMessage, setModel, onEditMessage, onDeleteMessage } = props
|
||||
const {
|
||||
message,
|
||||
index,
|
||||
model,
|
||||
isLastMessage,
|
||||
isAssistantMessage,
|
||||
assistantModel,
|
||||
setModel,
|
||||
onEditMessage,
|
||||
onDeleteMessage
|
||||
} = props
|
||||
const { t } = useTranslation()
|
||||
const [copied, setCopied] = useState(false)
|
||||
const [isTranslating, setIsTranslating] = useState(false)
|
||||
@ -157,11 +168,21 @@ const MessageMenubar: FC<Props> = (props) => {
|
||||
[handleTranslate, isTranslating, message, onEdit, onEditMessage, t]
|
||||
)
|
||||
|
||||
const onSelectModel = async () => {
|
||||
const onAtModelRegenerate = async () => {
|
||||
const selectedModel = await SelectModelPopup.show({ model })
|
||||
selectedModel && onRegenerate(selectedModel)
|
||||
}
|
||||
|
||||
const onDeleteAndRegenerate = () => {
|
||||
onEditMessage?.({
|
||||
...message,
|
||||
content: '',
|
||||
status: 'sending',
|
||||
modelId: assistantModel?.id || model?.id,
|
||||
translatedContent: undefined
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<MenusBar className={`menubar ${isLastMessage && 'show'}`}>
|
||||
{message.role === 'user' && (
|
||||
@ -177,10 +198,22 @@ const MessageMenubar: FC<Props> = (props) => {
|
||||
{copied && <CheckOutlined style={{ color: 'var(--color-primary)' }} />}
|
||||
</ActionButton>
|
||||
</Tooltip>
|
||||
{isAssistantMessage && (
|
||||
<Popconfirm
|
||||
title={t('message.regenerate.confirm')}
|
||||
okButtonProps={{ danger: true }}
|
||||
destroyTooltipOnHide
|
||||
icon={<QuestionCircleOutlined style={{ color: 'red' }} />}
|
||||
onConfirm={onDeleteAndRegenerate}>
|
||||
<ActionButton className="message-action-button">
|
||||
<SyncOutlined />
|
||||
</ActionButton>
|
||||
</Popconfirm>
|
||||
)}
|
||||
{canRegenerate && (
|
||||
<Tooltip title={t('common.regenerate')} mouseEnterDelay={0.8}>
|
||||
<ActionButton className="message-action-button" onClick={onSelectModel}>
|
||||
<SyncOutlined />
|
||||
<ActionButton className="message-action-button" onClick={onAtModelRegenerate}>
|
||||
<i className="iconfont icon-at1"></i>
|
||||
</ActionButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
@ -247,6 +280,9 @@ const ActionButton = styled.div`
|
||||
&:hover {
|
||||
color: var(--color-text-1);
|
||||
}
|
||||
.icon-at1 {
|
||||
font-size: 16px;
|
||||
}
|
||||
`
|
||||
|
||||
export default MessageMenubar
|
||||
|
||||
@ -55,7 +55,7 @@ const initialState: SettingsState = {
|
||||
theme: ThemeMode.auto,
|
||||
windowStyle: 'transparent',
|
||||
fontSize: 14,
|
||||
topicPosition: 'right',
|
||||
topicPosition: 'left',
|
||||
showTopicTime: false,
|
||||
pasteLongTextAsFile: false,
|
||||
clickAssistantToShowTopic: false,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user