feat: implement automatic text translation functionality

This commit is contained in:
kangfenmao 2024-12-03 11:55:52 +08:00
parent b73fa5a44f
commit 97052f825f
3 changed files with 89 additions and 26 deletions

View File

@ -63,8 +63,11 @@ electronDownload:
afterSign: scripts/notarize.js afterSign: scripts/notarize.js
releaseInfo: releaseInfo:
releaseNotes: | releaseNotes: |
支持清除应用缓存 输入内容支持快速翻译成英文
支持编辑翻译模型提示词 输出内容支持翻译成其他语言
支持使用搜索内容快速创建助手 快速敲击3次空格翻译
支持编辑模型是否为视觉模型 支持自定义快捷键
界面样式优化和错误修复 支持关闭对话自动重命名
修复 Gemini 自定义域名不生效问题
画图支持生成 Seed 种子词
修复 Markdown 渲染错误导致应用崩溃

View File

@ -69,27 +69,30 @@ const MessageMenubar: FC<Props> = (props) => {
editedText && onEditMessage?.({ ...message, content: editedText }) editedText && onEditMessage?.({ ...message, content: editedText })
}, [message, onEditMessage]) }, [message, onEditMessage])
const handleTranslate = async (language: string) => { const handleTranslate = useCallback(
if (isTranslating) return async (language: string) => {
if (isTranslating) return
onEditMessage?.({ ...message, translatedContent: t('translate.processing') }) onEditMessage?.({ ...message, translatedContent: t('translate.processing') })
setIsTranslating(true) setIsTranslating(true)
try { try {
const translatedText = await translateText(message.content, language) const translatedText = await translateText(message.content, language)
onEditMessage?.({ ...message, translatedContent: translatedText }) onEditMessage?.({ ...message, translatedContent: translatedText })
} catch (error) { } catch (error) {
console.error('Translation failed:', error) console.error('Translation failed:', error)
window.message.error({ window.message.error({
content: t('translate.error.failed'), content: t('translate.error.failed'),
key: 'translate-message' key: 'translate-message'
}) })
onEditMessage?.({ ...message, translatedContent: undefined }) onEditMessage?.({ ...message, translatedContent: undefined })
} finally { } finally {
setIsTranslating(false) setIsTranslating(false)
} }
} },
[isTranslating, message, onEditMessage, t]
)
const dropdownItems = useMemo( const dropdownItems = useMemo(
() => [ () => [

View File

@ -15,9 +15,11 @@ import { useTheme } from '@renderer/context/ThemeProvider'
import { usePaintings } from '@renderer/hooks/usePaintings' import { usePaintings } from '@renderer/hooks/usePaintings'
import { useAllProviders } from '@renderer/hooks/useProvider' import { useAllProviders } from '@renderer/hooks/useProvider'
import { useRuntime } from '@renderer/hooks/useRuntime' import { useRuntime } from '@renderer/hooks/useRuntime'
import { useSettings } from '@renderer/hooks/useSettings'
import AiProvider from '@renderer/providers/AiProvider' import AiProvider from '@renderer/providers/AiProvider'
import { getProviderByModel } from '@renderer/services/AssistantService' import { getProviderByModel } from '@renderer/services/AssistantService'
import FileManager from '@renderer/services/FileManager' import FileManager from '@renderer/services/FileManager'
import { translateText } from '@renderer/services/TranslateService'
import { useAppDispatch } from '@renderer/store' import { useAppDispatch } from '@renderer/store'
import { DEFAULT_PAINTING } from '@renderer/store/paintings' import { DEFAULT_PAINTING } from '@renderer/store/paintings'
import { setGenerating } from '@renderer/store/runtime' import { setGenerating } from '@renderer/store/runtime'
@ -25,7 +27,7 @@ import { FileType, Painting } from '@renderer/types'
import { getErrorMessage } from '@renderer/utils' import { getErrorMessage } from '@renderer/utils'
import { Button, Input, InputNumber, Radio, Select, Slider, Tooltip } from 'antd' import { Button, Input, InputNumber, Radio, Select, Slider, Tooltip } from 'antd'
import TextArea from 'antd/es/input/TextArea' import TextArea from 'antd/es/input/TextArea'
import { FC, useRef, useState } from 'react' import { FC, useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import styled from 'styled-components' import styled from 'styled-components'
@ -232,6 +234,59 @@ const PaintingsPage: FC = () => {
setCurrentImageIndex(0) setCurrentImageIndex(0)
} }
const { autoTranslateWithSpace } = useSettings()
const [spaceClickCount, setSpaceClickCount] = useState(0)
const [isTranslating, setIsTranslating] = useState(false)
const spaceClickTimer = useRef<NodeJS.Timeout>()
const translate = async () => {
if (isTranslating) {
return
}
if (!painting.prompt) {
return
}
try {
setIsTranslating(true)
const translatedText = await translateText(painting.prompt, 'english')
updatePaintingState({ prompt: translatedText })
} catch (error) {
console.error('Translation failed:', error)
} finally {
setIsTranslating(false)
}
}
const handleKeyDown = (event: React.KeyboardEvent<HTMLTextAreaElement>) => {
if (autoTranslateWithSpace && event.key === ' ') {
setSpaceClickCount((prev) => prev + 1)
if (spaceClickTimer.current) {
clearTimeout(spaceClickTimer.current)
}
spaceClickTimer.current = setTimeout(() => {
setSpaceClickCount(0)
}, 200)
if (spaceClickCount === 2) {
setSpaceClickCount(0)
setIsTranslating(true)
translate()
}
}
}
useEffect(() => {
return () => {
if (spaceClickTimer.current) {
clearTimeout(spaceClickTimer.current)
}
}
}, [])
return ( return (
<Container> <Container>
<Navbar> <Navbar>
@ -362,14 +417,16 @@ const PaintingsPage: FC = () => {
disabled={isLoading} disabled={isLoading}
value={painting.prompt} value={painting.prompt}
onChange={(e) => updatePaintingState({ prompt: e.target.value })} onChange={(e) => updatePaintingState({ prompt: e.target.value })}
placeholder={t('paintings.prompt_placeholder')} placeholder={isTranslating ? t('paintings.translating') : t('paintings.prompt_placeholder')}
onKeyDown={handleKeyDown}
/> />
<Toolbar> <Toolbar>
<ToolbarMenu> <ToolbarMenu>
<TranslateButton <TranslateButton
text={textareaRef.current?.resizableTextArea?.textArea?.value} text={textareaRef.current?.resizableTextArea?.textArea?.value}
onTranslated={(translatedText) => updatePaintingState({ prompt: translatedText })} onTranslated={(translatedText) => updatePaintingState({ prompt: translatedText })}
disabled={isLoading} disabled={isLoading || isTranslating}
isLoading={isTranslating}
style={{ marginRight: 6, borderRadius: '50%' }} style={{ marginRight: 6, borderRadius: '50%' }}
/> />
<SendMessageButton sendMessage={onGenerate} disabled={isLoading} /> <SendMessageButton sendMessage={onGenerate} disabled={isLoading} />