feat: edit message

This commit is contained in:
kangfenmao 2024-09-26 22:45:59 +08:00
parent 1035019fc2
commit fcc627db6f
5 changed files with 137 additions and 7 deletions

View File

@ -0,0 +1,98 @@
import { Modal, ModalProps } from 'antd'
import TextArea from 'antd/es/input/TextArea'
import { TextAreaProps } from 'antd/lib/input'
import { TextAreaRef } from 'antd/lib/input/TextArea'
import { useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { TopView } from '../TopView'
interface ShowParams {
text: string
textareaProps?: TextAreaProps
modalProps?: ModalProps
}
interface Props extends ShowParams {
resolve: (data: any) => void
}
const PopupContainer: React.FC<Props> = ({ text, textareaProps, modalProps, resolve }) => {
const [open, setOpen] = useState(true)
const { t } = useTranslation()
const [textValue, setTextValue] = useState(text)
const textareaRef = useRef<TextAreaRef>(null)
const onOk = () => {
setOpen(false)
resolve(textValue)
}
const onCancel = () => {
setOpen(false)
}
const onClose = () => {
resolve(null)
}
const resizeTextArea = () => {
const textArea = textareaRef.current?.resizableTextArea?.textArea
const maxHeight = innerHeight * 0.6
if (textArea) {
textArea.style.height = 'auto'
textArea.style.height = textArea?.scrollHeight > maxHeight ? maxHeight + 'px' : `${textArea?.scrollHeight}px`
}
}
useEffect(() => {
setTimeout(resizeTextArea, 0)
}, [])
return (
<Modal
title={t('common.edit')}
width="60vw"
style={{ maxHeight: '70vh' }}
transitionName="ant-move-down"
maskTransitionName="ant-fade"
okText={t('common.save')}
{...modalProps}
open={open}
onOk={onOk}
onCancel={onCancel}
afterClose={onClose}
centered>
<TextArea
ref={textareaRef}
rows={4}
autoFocus
{...textareaProps}
value={textValue}
onInput={resizeTextArea}
onChange={(e) => setTextValue(e.target.value)}
/>
</Modal>
)
}
export default class TextEditPopup {
static topviewId = 0
static hide() {
TopView.hide('TextEditPopup')
}
static show(props: ShowParams) {
return new Promise<any>((resolve) => {
TopView.show(
<PopupContainer
{...props}
resolve={(v) => {
resolve(v)
this.hide()
}}
/>,
'TextEditPopup'
)
})
}
}

View File

@ -309,6 +309,7 @@ const resources = {
regenerate: '重新生成',
provider: '提供商',
you: '用户',
save: '保存',
footnote: '引用内容',
select: '选择',
search: '搜索',

View File

@ -28,10 +28,11 @@ interface Props {
index?: number
total?: number
lastMessage?: boolean
onEditMessage?: (message: Message) => void
onDeleteMessage?: (message: Message) => void
}
const MessageItem: FC<Props> = ({ message, index, lastMessage, onDeleteMessage }) => {
const MessageItem: FC<Props> = ({ message, index, lastMessage, onEditMessage, onDeleteMessage }) => {
const avatar = useAvatar()
const { t } = useTranslation()
const { assistant, setModel } = useAssistant(message.assistantId)
@ -117,6 +118,7 @@ const MessageItem: FC<Props> = ({ message, index, lastMessage, onDeleteMessage }
isLastMessage={isLastMessage}
isAssistantMessage={isAssistantMessage}
setModel={setModel}
onEditMessage={onEditMessage}
onDeleteMessage={onDeleteMessage}
/>
</MessageFooter>
@ -126,7 +128,10 @@ const MessageItem: FC<Props> = ({ message, index, lastMessage, onDeleteMessage }
)
}
const MessageContent: React.FC<{ message: Message; model?: Model }> = ({ message, model }) => {
const MessageContent: React.FC<{
message: Message
model?: Model
}> = ({ message, model }) => {
const { t } = useTranslation()
if (message.status === 'sending') {

View File

@ -8,6 +8,7 @@ import {
SaveOutlined,
SyncOutlined
} from '@ant-design/icons'
import TextEditPopup from '@renderer/components/Popups/TextEditPopup'
import { EVENT_NAMES, EventEmitter } from '@renderer/services/event'
import { Message, Model } from '@renderer/types'
import { removeTrailingDoubleSpaces } from '@renderer/utils'
@ -25,19 +26,18 @@ interface Props {
isLastMessage: boolean
isAssistantMessage: boolean
setModel: (model: Model) => void
onEditMessage?: (message: Message) => void
onDeleteMessage?: (message: Message) => void
}
const MessageMenubar: FC<Props> = (props) => {
const { message, index, model, isLastMessage, isAssistantMessage, setModel, onDeleteMessage } = props
const { message, index, model, isLastMessage, isAssistantMessage, setModel, onEditMessage, onDeleteMessage } = props
const { t } = useTranslation()
const [copied, setCopied] = useState(false)
const isUserMessage = message.role === 'user'
const canRegenerate = isLastMessage && isAssistantMessage
const onEdit = useCallback(() => EventEmitter.emit(EVENT_NAMES.EDIT_MESSAGE, message), [message])
const onCopy = useCallback(() => {
navigator.clipboard.writeText(removeTrailingDoubleSpaces(message.content))
window.message.success({ content: t('message.copied'), key: 'copy-message' })
@ -57,6 +57,11 @@ const MessageMenubar: FC<Props> = (props) => {
EventEmitter.emit(EVENT_NAMES.NEW_BRANCH, index)
}, [index])
const onEdit = useCallback(async () => {
const editedText = await TextEditPopup.show({ text: message.content })
editedText && onEditMessage?.({ ...message, content: editedText })
}, [message, onEditMessage])
const dropdownItems = useMemo(
() => [
{
@ -67,9 +72,15 @@ const MessageMenubar: FC<Props> = (props) => {
const fileName = message.createdAt + '.md'
window.api.file.save(fileName, message.content)
}
},
{
label: t('common.edit'),
key: 'edit',
icon: <EditOutlined />,
onClick: onEdit
}
],
[t, message]
[message.content, message.createdAt, onEdit, t]
)
return (

View File

@ -69,6 +69,15 @@ const Messages: FC<Props> = ({ assistant, topic, setActiveTopic }) => {
[messages, topic.id]
)
const onEditMessage = useCallback(
(message: Message) => {
const _messages = messages.map((m) => (m.id === message.id ? message : m))
setMessages(_messages)
db.topics.update(topic.id, { messages: _messages })
},
[messages, topic.id]
)
useEffect(() => {
const unsubscribes = [
EventEmitter.on(EVENT_NAMES.SEND_MESSAGE, async (msg: Message) => {
@ -199,7 +208,13 @@ const Messages: FC<Props> = ({ assistant, topic, setActiveTopic }) => {
<Suggestions assistant={assistant} messages={messages} lastMessage={lastMessage} />
{lastMessage && <MessageItem key={lastMessage.id} message={lastMessage} lastMessage />}
{reverse([...messages]).map((message, index) => (
<MessageItem key={message.id} message={message} index={index} onDeleteMessage={onDeleteMessage} />
<MessageItem
key={message.id}
message={message}
index={index}
onEditMessage={onEditMessage}
onDeleteMessage={onDeleteMessage}
/>
))}
<Prompt assistant={assistant} key={assistant.prompt} />
</Container>