feat: add chat message translate copy button (#4620)
* feat: add chat message translate copy button * Update MessageMenubar.tsx * fix: copy button display
This commit is contained in:
parent
d907344ca7
commit
8b462935b4
@ -1,5 +1,6 @@
|
|||||||
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
|
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
|
||||||
import { estimateMessageUsage } from '@renderer/services/TokenService'
|
import { estimateMessageUsage } from '@renderer/services/TokenService'
|
||||||
|
import { translateText } from '@renderer/services/TranslateService'
|
||||||
import store, { useAppDispatch, useAppSelector } from '@renderer/store'
|
import store, { useAppDispatch, useAppSelector } from '@renderer/store'
|
||||||
import {
|
import {
|
||||||
clearStreamMessage,
|
clearStreamMessage,
|
||||||
@ -18,8 +19,10 @@ import {
|
|||||||
import type { Assistant, Message, Topic } from '@renderer/types'
|
import type { Assistant, Message, Topic } from '@renderer/types'
|
||||||
import { abortCompletion } from '@renderer/utils/abortController'
|
import { abortCompletion } from '@renderer/utils/abortController'
|
||||||
import { useCallback } from 'react'
|
import { useCallback } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
import { TopicManager } from './useTopic'
|
import { TopicManager } from './useTopic'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 自定义Hook,提供消息操作相关的功能
|
* 自定义Hook,提供消息操作相关的功能
|
||||||
*
|
*
|
||||||
@ -28,6 +31,7 @@ import { TopicManager } from './useTopic'
|
|||||||
*/
|
*/
|
||||||
export function useMessageOperations(topic: Topic) {
|
export function useMessageOperations(topic: Topic) {
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除单个消息
|
* 删除单个消息
|
||||||
@ -60,8 +64,7 @@ export function useMessageOperations(topic: Topic) {
|
|||||||
const message = messages?.find((m) => m.id === messageId)
|
const message = messages?.find((m) => m.id === messageId)
|
||||||
if (message) {
|
if (message) {
|
||||||
const updatedMessage = { ...message, ...updates }
|
const updatedMessage = { ...message, ...updates }
|
||||||
const usage = await estimateMessageUsage(updatedMessage)
|
updates.usage = await estimateMessageUsage(updatedMessage)
|
||||||
updates.usage = usage
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await dispatch(updateMessageThunk(topic.id, messageId, updates))
|
await dispatch(updateMessageThunk(topic.id, messageId, updates))
|
||||||
@ -128,7 +131,7 @@ export function useMessageOperations(topic: Topic) {
|
|||||||
const clearTopicMessagesAction = useCallback(
|
const clearTopicMessagesAction = useCallback(
|
||||||
async (_topicId?: string) => {
|
async (_topicId?: string) => {
|
||||||
const topicId = _topicId || topic.id
|
const topicId = _topicId || topic.id
|
||||||
await dispatch(clearTopicMessages(topicId))
|
dispatch(clearTopicMessages(topicId))
|
||||||
await TopicManager.clearTopicMessages(topicId)
|
await TopicManager.clearTopicMessages(topicId)
|
||||||
},
|
},
|
||||||
[dispatch, topic.id]
|
[dispatch, topic.id]
|
||||||
@ -148,7 +151,7 @@ export function useMessageOperations(topic: Topic) {
|
|||||||
* 创建新的上下文(clear message)
|
* 创建新的上下文(clear message)
|
||||||
*/
|
*/
|
||||||
const createNewContext = useCallback(async () => {
|
const createNewContext = useCallback(async () => {
|
||||||
EventEmitter.emit(EVENT_NAMES.NEW_CONTEXT)
|
await EventEmitter.emit(EVENT_NAMES.NEW_CONTEXT)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const displayCount = useAppSelector(selectDisplayCount)
|
const displayCount = useAppSelector(selectDisplayCount)
|
||||||
@ -190,6 +193,28 @@ export function useMessageOperations(topic: Topic) {
|
|||||||
dispatch(setTopicLoading({ topicId: topic.id, loading: false }))
|
dispatch(setTopicLoading({ topicId: topic.id, loading: false }))
|
||||||
}, [topic.id, dispatch])
|
}, [topic.id, dispatch])
|
||||||
|
|
||||||
|
const translateMessage = useCallback(
|
||||||
|
async (messageId: string, language: string) => {
|
||||||
|
const messages = store.getState().messages.messagesByTopic[topic.id]
|
||||||
|
const message = messages?.find((m) => m.id === messageId)
|
||||||
|
if (!message) return
|
||||||
|
|
||||||
|
translateText(message.content, language, (text) => {
|
||||||
|
setStreamMessageAction({ ...message, translatedContent: text })
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
commitStreamMessageAction(messageId)
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Translation failed:', error)
|
||||||
|
window.message.error({ content: t('translate.error.failed'), key: 'translate-message' })
|
||||||
|
editMessage(messageId, { translatedContent: undefined })
|
||||||
|
clearStreamMessageAction(messageId)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
[topic.id, editMessage, t, clearStreamMessageAction, setStreamMessageAction, commitStreamMessageAction]
|
||||||
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 恢复/重发消息
|
* 恢复/重发消息
|
||||||
* 暂时不需要
|
* 暂时不需要
|
||||||
@ -216,16 +241,15 @@ export function useMessageOperations(topic: Topic) {
|
|||||||
clearTopicMessages: clearTopicMessagesAction,
|
clearTopicMessages: clearTopicMessagesAction,
|
||||||
// pauseMessage,
|
// pauseMessage,
|
||||||
pauseMessages,
|
pauseMessages,
|
||||||
resumeMessage
|
resumeMessage,
|
||||||
|
translateMessage
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useTopicMessages = (topic: Topic) => {
|
export const useTopicMessages = (topic: Topic) => {
|
||||||
const messages = useAppSelector((state) => selectTopicMessages(state, topic.id))
|
return useAppSelector((state) => selectTopicMessages(state, topic.id))
|
||||||
return messages
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useTopicLoading = (topic: Topic) => {
|
export const useTopicLoading = (topic: Topic) => {
|
||||||
const loading = useAppSelector((state) => selectTopicLoading(state, topic.id))
|
return useAppSelector((state) => selectTopicLoading(state, topic.id))
|
||||||
return loading
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -561,6 +561,7 @@
|
|||||||
"error": "Error occurred"
|
"error": "Error occurred"
|
||||||
},
|
},
|
||||||
"topic.added": "New topic added",
|
"topic.added": "New topic added",
|
||||||
|
"translation.copied": "Translation copied",
|
||||||
"upgrade.success.button": "Restart",
|
"upgrade.success.button": "Restart",
|
||||||
"upgrade.success.content": "Please restart the application to complete the upgrade",
|
"upgrade.success.content": "Please restart the application to complete the upgrade",
|
||||||
"upgrade.success.title": "Upgrade successfully",
|
"upgrade.success.title": "Upgrade successfully",
|
||||||
@ -1407,6 +1408,7 @@
|
|||||||
"content": "Translation will replace the original text, continue?",
|
"content": "Translation will replace the original text, continue?",
|
||||||
"title": "Translation Confirmation"
|
"title": "Translation Confirmation"
|
||||||
},
|
},
|
||||||
|
"copy": "Copy",
|
||||||
"error.failed": "Translation failed",
|
"error.failed": "Translation failed",
|
||||||
"error.not_configured": "Translation model is not configured",
|
"error.not_configured": "Translation model is not configured",
|
||||||
"history": {
|
"history": {
|
||||||
|
|||||||
@ -560,6 +560,7 @@
|
|||||||
"error": "エラーが発生しました"
|
"error": "エラーが発生しました"
|
||||||
},
|
},
|
||||||
"topic.added": "新しいトピックが追加されました",
|
"topic.added": "新しいトピックが追加されました",
|
||||||
|
"translation.copied": "翻訳結果をコピーしました",
|
||||||
"upgrade.success.button": "再起動",
|
"upgrade.success.button": "再起動",
|
||||||
"upgrade.success.content": "アップグレードを完了するためにアプリケーションを再起動してください",
|
"upgrade.success.content": "アップグレードを完了するためにアプリケーションを再起動してください",
|
||||||
"upgrade.success.title": "アップグレードに成功しました",
|
"upgrade.success.title": "アップグレードに成功しました",
|
||||||
@ -1407,6 +1408,7 @@
|
|||||||
"content": "翻訳すると元のテキストが上書きされます。続行しますか?",
|
"content": "翻訳すると元のテキストが上書きされます。続行しますか?",
|
||||||
"title": "翻訳確認"
|
"title": "翻訳確認"
|
||||||
},
|
},
|
||||||
|
"copy": "翻訳をコピー",
|
||||||
"error.failed": "翻訳に失敗しました",
|
"error.failed": "翻訳に失敗しました",
|
||||||
"error.not_configured": "翻訳モデルが設定されていません",
|
"error.not_configured": "翻訳モデルが設定されていません",
|
||||||
"history": {
|
"history": {
|
||||||
|
|||||||
@ -561,6 +561,7 @@
|
|||||||
"error": "Произошла ошибка"
|
"error": "Произошла ошибка"
|
||||||
},
|
},
|
||||||
"topic.added": "Новый топик добавлен",
|
"topic.added": "Новый топик добавлен",
|
||||||
|
"translation.copied": "Перевод скопирован",
|
||||||
"upgrade.success.button": "Перезапустить",
|
"upgrade.success.button": "Перезапустить",
|
||||||
"upgrade.success.content": "Пожалуйста, перезапустите приложение для завершения обновления",
|
"upgrade.success.content": "Пожалуйста, перезапустите приложение для завершения обновления",
|
||||||
"upgrade.success.title": "Обновление успешно",
|
"upgrade.success.title": "Обновление успешно",
|
||||||
@ -1407,6 +1408,7 @@
|
|||||||
"content": "Перевод заменит исходный текст, продолжить?",
|
"content": "Перевод заменит исходный текст, продолжить?",
|
||||||
"title": "Перевод подтверждение"
|
"title": "Перевод подтверждение"
|
||||||
},
|
},
|
||||||
|
"copy": "Копировать перевод",
|
||||||
"error.failed": "Перевод не удалось",
|
"error.failed": "Перевод не удалось",
|
||||||
"error.not_configured": "Модель перевода не настроена",
|
"error.not_configured": "Модель перевода не настроена",
|
||||||
"history": {
|
"history": {
|
||||||
|
|||||||
@ -561,6 +561,7 @@
|
|||||||
"error": "发生错误"
|
"error": "发生错误"
|
||||||
},
|
},
|
||||||
"topic.added": "话题添加成功",
|
"topic.added": "话题添加成功",
|
||||||
|
"translation.copied": "翻译已复制",
|
||||||
"upgrade.success.button": "重启",
|
"upgrade.success.button": "重启",
|
||||||
"upgrade.success.content": "重启用以完成升级",
|
"upgrade.success.content": "重启用以完成升级",
|
||||||
"upgrade.success.title": "升级成功",
|
"upgrade.success.title": "升级成功",
|
||||||
@ -1407,6 +1408,7 @@
|
|||||||
"content": "翻译后将覆盖原文,是否继续?",
|
"content": "翻译后将覆盖原文,是否继续?",
|
||||||
"title": "翻译确认"
|
"title": "翻译确认"
|
||||||
},
|
},
|
||||||
|
"copy": "复制翻译",
|
||||||
"error.failed": "翻译失败",
|
"error.failed": "翻译失败",
|
||||||
"error.not_configured": "翻译模型未配置",
|
"error.not_configured": "翻译模型未配置",
|
||||||
"history": {
|
"history": {
|
||||||
|
|||||||
@ -560,6 +560,7 @@
|
|||||||
"invoking": "調用中",
|
"invoking": "調用中",
|
||||||
"error": "發生錯誤"
|
"error": "發生錯誤"
|
||||||
},
|
},
|
||||||
|
"translation.copied": "翻譯已複製",
|
||||||
"topic.added": "新話題已新增",
|
"topic.added": "新話題已新增",
|
||||||
"upgrade.success.button": "重新啟動",
|
"upgrade.success.button": "重新啟動",
|
||||||
"upgrade.success.content": "請重新啟動程式以完成升級",
|
"upgrade.success.content": "請重新啟動程式以完成升級",
|
||||||
@ -1407,6 +1408,7 @@
|
|||||||
"content": "翻譯後將覆蓋原文,是否繼續?",
|
"content": "翻譯後將覆蓋原文,是否繼續?",
|
||||||
"title": "翻譯確認"
|
"title": "翻譯確認"
|
||||||
},
|
},
|
||||||
|
"copy": "複製翻譯",
|
||||||
"error.failed": "翻譯失敗",
|
"error.failed": "翻譯失敗",
|
||||||
"error.not_configured": "翻譯模型未設定",
|
"error.not_configured": "翻譯模型未設定",
|
||||||
"history": {
|
"history": {
|
||||||
|
|||||||
@ -181,7 +181,7 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
EventEmitter.emit(EVENT_NAMES.SEND_MESSAGE)
|
await EventEmitter.emit(EVENT_NAMES.SEND_MESSAGE)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Dispatch the sendMessage action with all options
|
// Dispatch the sendMessage action with all options
|
||||||
@ -209,7 +209,7 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
|
|||||||
userMessage.usage = await estimateMessageUsage(userMessage)
|
userMessage.usage = await estimateMessageUsage(userMessage)
|
||||||
currentMessageId.current = userMessage.id
|
currentMessageId.current = userMessage.id
|
||||||
|
|
||||||
dispatch(
|
await dispatch(
|
||||||
_sendMessage(userMessage, assistant, topic, {
|
_sendMessage(userMessage, assistant, topic, {
|
||||||
mentions: mentionModels
|
mentions: mentionModels
|
||||||
})
|
})
|
||||||
@ -525,7 +525,7 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
|
|||||||
await onPause()
|
await onPause()
|
||||||
await delay(1)
|
await delay(1)
|
||||||
}
|
}
|
||||||
EventEmitter.emit(EVENT_NAMES.CLEAR_MESSAGES)
|
await EventEmitter.emit(EVENT_NAMES.CLEAR_MESSAGES)
|
||||||
}
|
}
|
||||||
|
|
||||||
const onNewContext = () => {
|
const onNewContext = () => {
|
||||||
|
|||||||
@ -7,7 +7,6 @@ import { TranslateLanguageOptions } from '@renderer/config/translate'
|
|||||||
import { useMessageOperations, useTopicLoading } from '@renderer/hooks/useMessageOperations'
|
import { useMessageOperations, useTopicLoading } from '@renderer/hooks/useMessageOperations'
|
||||||
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
|
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
|
||||||
import { getMessageTitle, resetAssistantMessage } from '@renderer/services/MessagesService'
|
import { getMessageTitle, resetAssistantMessage } from '@renderer/services/MessagesService'
|
||||||
import { translateText } from '@renderer/services/TranslateService'
|
|
||||||
import { RootState } from '@renderer/store'
|
import { RootState } from '@renderer/store'
|
||||||
import type { Message, Model } from '@renderer/types'
|
import type { Message, Model } from '@renderer/types'
|
||||||
import type { Assistant, Topic } from '@renderer/types'
|
import type { Assistant, Topic } from '@renderer/types'
|
||||||
@ -37,7 +36,7 @@ import {
|
|||||||
ThumbsUp,
|
ThumbsUp,
|
||||||
Trash
|
Trash
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
import { FC, memo, useCallback, useMemo, useState } from 'react'
|
import React, { FC, memo, useCallback, useMemo, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
@ -60,12 +59,11 @@ const MessageMenubar: FC<Props> = (props) => {
|
|||||||
props
|
props
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [copied, setCopied] = useState(false)
|
const [copied, setCopied] = useState(false)
|
||||||
const [isTranslating, setIsTranslating] = useState(false)
|
const [translationCopied, setTranslationCopied] = useState(false)
|
||||||
const [showRegenerateTooltip, setShowRegenerateTooltip] = useState(false)
|
const [showRegenerateTooltip, setShowRegenerateTooltip] = useState(false)
|
||||||
const [showDeleteTooltip, setShowDeleteTooltip] = useState(false)
|
const [showDeleteTooltip, setShowDeleteTooltip] = useState(false)
|
||||||
const assistantModel = assistant?.model
|
const assistantModel = assistant?.model
|
||||||
const { editMessage, setStreamMessage, deleteMessage, resendMessage, commitStreamMessage, clearStreamMessage } =
|
const { editMessage, deleteMessage, resendMessage, translateMessage } = useMessageOperations(topic)
|
||||||
useMessageOperations(topic)
|
|
||||||
const loading = useTopicLoading(topic)
|
const loading = useTopicLoading(topic)
|
||||||
|
|
||||||
const isUserMessage = message.role === 'user'
|
const isUserMessage = message.role === 'user'
|
||||||
@ -92,9 +90,23 @@ const MessageMenubar: FC<Props> = (props) => {
|
|||||||
[message, t]
|
[message, t]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const onCopyTranslation = useCallback(
|
||||||
|
(e: React.MouseEvent) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
|
||||||
|
if (message.translatedContent) {
|
||||||
|
navigator.clipboard.writeText(removeTrailingDoubleSpaces(message.translatedContent.trimStart()))
|
||||||
|
window.message.success({ content: t('message.translation.copied'), key: 'copy-translation' })
|
||||||
|
setTranslationCopied(true)
|
||||||
|
setTimeout(() => setTranslationCopied(false), 2000)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[message.translatedContent, t]
|
||||||
|
)
|
||||||
|
|
||||||
const onNewBranch = useCallback(async () => {
|
const onNewBranch = useCallback(async () => {
|
||||||
if (loading) return
|
if (loading) return
|
||||||
EventEmitter.emit(EVENT_NAMES.NEW_BRANCH, index)
|
await EventEmitter.emit(EVENT_NAMES.NEW_BRANCH, index)
|
||||||
window.message.success({ content: t('chat.message.new.branch.created'), key: 'new-branch' })
|
window.message.success({ content: t('chat.message.new.branch.created'), key: 'new-branch' })
|
||||||
}, [index, t, loading])
|
}, [index, t, loading])
|
||||||
|
|
||||||
@ -144,7 +156,7 @@ const MessageMenubar: FC<Props> = (props) => {
|
|||||||
|
|
||||||
if (editedText && editedText !== textToEdit) {
|
if (editedText && editedText !== textToEdit) {
|
||||||
// 解析编辑后的文本,提取图片 URL
|
// 解析编辑后的文本,提取图片 URL
|
||||||
const imageRegex = /!\[image-\d+\]\((.*?)\)/g
|
const imageRegex = /!\[image-\d+]\((.*?)\)/g
|
||||||
const imageUrls: string[] = []
|
const imageUrls: string[] = []
|
||||||
let match
|
let match
|
||||||
let content = editedText
|
let content = editedText
|
||||||
@ -170,7 +182,7 @@ const MessageMenubar: FC<Props> = (props) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
resendMessage &&
|
resendMessage &&
|
||||||
handleResendUserMessage({
|
(await handleResendUserMessage({
|
||||||
...message,
|
...message,
|
||||||
content: content.trim(),
|
content: content.trim(),
|
||||||
metadata: {
|
metadata: {
|
||||||
@ -183,38 +195,10 @@ const MessageMenubar: FC<Props> = (props) => {
|
|||||||
}
|
}
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
})
|
}))
|
||||||
}
|
}
|
||||||
}, [message, editMessage, handleResendUserMessage, t])
|
}, [message, editMessage, handleResendUserMessage, t])
|
||||||
|
|
||||||
const handleTranslate = useCallback(
|
|
||||||
async (language: string) => {
|
|
||||||
if (isTranslating) return
|
|
||||||
|
|
||||||
editMessage(message.id, { translatedContent: t('translate.processing') })
|
|
||||||
|
|
||||||
setIsTranslating(true)
|
|
||||||
|
|
||||||
try {
|
|
||||||
await translateText(message.content, language, (text) => {
|
|
||||||
// 使用 setStreamMessage 来更新翻译内容
|
|
||||||
setStreamMessage({ ...message, translatedContent: text })
|
|
||||||
})
|
|
||||||
|
|
||||||
// 翻译完成后,提交流消息
|
|
||||||
commitStreamMessage(message.id)
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Translation failed:', error)
|
|
||||||
window.message.error({ content: t('translate.error.failed'), key: 'translate-message' })
|
|
||||||
editMessage(message.id, { translatedContent: undefined })
|
|
||||||
clearStreamMessage(message.id)
|
|
||||||
} finally {
|
|
||||||
setIsTranslating(false)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[isTranslating, message, editMessage, setStreamMessage, commitStreamMessage, clearStreamMessage, t]
|
|
||||||
)
|
|
||||||
|
|
||||||
const dropdownItems = useMemo(
|
const dropdownItems = useMemo(
|
||||||
() => [
|
() => [
|
||||||
{
|
{
|
||||||
@ -281,7 +265,7 @@ const MessageMenubar: FC<Props> = (props) => {
|
|||||||
onClick: async () => {
|
onClick: async () => {
|
||||||
const markdown = messageToMarkdown(message)
|
const markdown = messageToMarkdown(message)
|
||||||
const title = await getMessageTitle(message)
|
const title = await getMessageTitle(message)
|
||||||
window.api.export.toWord(markdown, title)
|
await window.api.export.toWord(markdown, title)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
exportMenuOptions.notion && {
|
exportMenuOptions.notion && {
|
||||||
@ -290,7 +274,7 @@ const MessageMenubar: FC<Props> = (props) => {
|
|||||||
onClick: async () => {
|
onClick: async () => {
|
||||||
const title = await getMessageTitle(message)
|
const title = await getMessageTitle(message)
|
||||||
const markdown = messageToMarkdown(message)
|
const markdown = messageToMarkdown(message)
|
||||||
exportMarkdownToNotion(title, markdown)
|
await exportMarkdownToNotion(title, markdown)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
exportMenuOptions.yuque && {
|
exportMenuOptions.yuque && {
|
||||||
@ -299,7 +283,7 @@ const MessageMenubar: FC<Props> = (props) => {
|
|||||||
onClick: async () => {
|
onClick: async () => {
|
||||||
const title = await getMessageTitle(message)
|
const title = await getMessageTitle(message)
|
||||||
const markdown = messageToMarkdown(message)
|
const markdown = messageToMarkdown(message)
|
||||||
exportMarkdownToYuque(title, markdown)
|
await exportMarkdownToYuque(title, markdown)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
exportMenuOptions.obsidian && {
|
exportMenuOptions.obsidian && {
|
||||||
@ -317,7 +301,7 @@ const MessageMenubar: FC<Props> = (props) => {
|
|||||||
onClick: async () => {
|
onClick: async () => {
|
||||||
const title = await getMessageTitle(message)
|
const title = await getMessageTitle(message)
|
||||||
const markdown = messageToMarkdown(message)
|
const markdown = messageToMarkdown(message)
|
||||||
exportMarkdownToJoplin(title, markdown)
|
await exportMarkdownToJoplin(title, markdown)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
exportMenuOptions.siyuan && {
|
exportMenuOptions.siyuan && {
|
||||||
@ -326,7 +310,7 @@ const MessageMenubar: FC<Props> = (props) => {
|
|||||||
onClick: async () => {
|
onClick: async () => {
|
||||||
const title = await getMessageTitle(message)
|
const title = await getMessageTitle(message)
|
||||||
const markdown = messageToMarkdown(message)
|
const markdown = messageToMarkdown(message)
|
||||||
exportMarkdownToSiyuan(title, markdown)
|
await exportMarkdownToSiyuan(title, markdown)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
].filter(Boolean)
|
].filter(Boolean)
|
||||||
@ -340,8 +324,8 @@ const MessageMenubar: FC<Props> = (props) => {
|
|||||||
if (loading) return
|
if (loading) return
|
||||||
const selectedModel = isGrouped ? model : assistantModel
|
const selectedModel = isGrouped ? model : assistantModel
|
||||||
const _message = resetAssistantMessage(message, selectedModel)
|
const _message = resetAssistantMessage(message, selectedModel)
|
||||||
editMessage(message.id, { ..._message })
|
await editMessage(message.id, { ..._message })
|
||||||
resendMessage(_message, assistant)
|
await resendMessage(_message, assistant)
|
||||||
}
|
}
|
||||||
|
|
||||||
const onMentionModel = async (e: React.MouseEvent) => {
|
const onMentionModel = async (e: React.MouseEvent) => {
|
||||||
@ -349,7 +333,7 @@ const MessageMenubar: FC<Props> = (props) => {
|
|||||||
if (loading) return
|
if (loading) return
|
||||||
const selectedModel = await SelectModelPopup.show({ model })
|
const selectedModel = await SelectModelPopup.show({ model })
|
||||||
if (!selectedModel) return
|
if (!selectedModel) return
|
||||||
resendMessage(message, { ...assistant, model: selectedModel }, true)
|
await resendMessage(message, { ...assistant, model: selectedModel }, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
const onUseful = useCallback(
|
const onUseful = useCallback(
|
||||||
@ -414,13 +398,26 @@ const MessageMenubar: FC<Props> = (props) => {
|
|||||||
...TranslateLanguageOptions.map((item) => ({
|
...TranslateLanguageOptions.map((item) => ({
|
||||||
label: item.emoji + ' ' + item.label,
|
label: item.emoji + ' ' + item.label,
|
||||||
key: item.value,
|
key: item.value,
|
||||||
onClick: () => handleTranslate(item.value)
|
onClick: () => translateMessage(message.id, item.value)
|
||||||
})),
|
})),
|
||||||
{
|
{
|
||||||
label: '✖ ' + t('translate.close'),
|
label: '✖ ' + t('translate.close'),
|
||||||
key: 'translate-close',
|
key: 'translate-close',
|
||||||
onClick: () => editMessage(message.id, { translatedContent: undefined })
|
onClick: () => editMessage(message.id, { translatedContent: undefined })
|
||||||
|
},
|
||||||
|
...(message.translatedContent
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
label: '📋 ' + t('translate.copy'),
|
||||||
|
key: 'translate-copy',
|
||||||
|
icon: translationCopied ? <CheckOutlined style={{ color: 'var(--color-primary)' }} /> : null,
|
||||||
|
onClick: (e) => {
|
||||||
|
e.domEvent.stopPropagation()
|
||||||
|
onCopyTranslation(e.domEvent as unknown as React.MouseEvent)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
: [])
|
||||||
],
|
],
|
||||||
onClick: (e) => e.domEvent.stopPropagation()
|
onClick: (e) => e.domEvent.stopPropagation()
|
||||||
}}
|
}}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user