From e506460ef9cafb6f1e52322f83085f965a1b7b0c Mon Sep 17 00:00:00 2001 From: chenxi <16267732+chenxi-null@users.noreply.github.com> Date: Mon, 5 May 2025 14:58:57 +0800 Subject: [PATCH] feat: popup question editor support translation assistant (#5660) * feat: TextEditPopup support translation assistant * polish: ensure safe state updates in TextEditPopup during unmount * test: make delay assertion more lenient in unclassified utils tests * feat: add loading indicator to translation button in TextEditPopup --- .../src/components/Popups/TextEditPopup.tsx | 116 ++++++++++++++++-- .../src/utils/__tests__/index.test.ts | 5 +- 2 files changed, 110 insertions(+), 11 deletions(-) diff --git a/src/renderer/src/components/Popups/TextEditPopup.tsx b/src/renderer/src/components/Popups/TextEditPopup.tsx index ff19c2554a..f2ecae4fc6 100644 --- a/src/renderer/src/components/Popups/TextEditPopup.tsx +++ b/src/renderer/src/components/Popups/TextEditPopup.tsx @@ -1,7 +1,13 @@ +import { LoadingOutlined } from '@ant-design/icons' +import { useDefaultModel } from '@renderer/hooks/useAssistant' +import { useSettings } from '@renderer/hooks/useSettings' +import { fetchTranslate } from '@renderer/services/ApiService' +import { getDefaultTranslateAssistant } from '@renderer/services/AssistantService' 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 { Languages } from 'lucide-react' import { useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import styled from 'styled-components' @@ -23,7 +29,17 @@ const PopupContainer: React.FC = ({ text, textareaProps, modalProps, reso const [open, setOpen] = useState(true) const { t } = useTranslation() const [textValue, setTextValue] = useState(text) + const [isTranslating, setIsTranslating] = useState(false) const textareaRef = useRef(null) + const { translateModel } = useDefaultModel() + const { targetLanguage, showTranslateConfirm } = useSettings() + const isMounted = useRef(true) + + useEffect(() => { + return () => { + isMounted.current = false + } + }, []) const onOk = () => { setOpen(false) @@ -62,6 +78,49 @@ const PopupContainer: React.FC = ({ text, textareaProps, modalProps, reso } } + const handleTranslate = async () => { + if (!textValue.trim() || isTranslating) return + + if (showTranslateConfirm) { + const confirmed = await window?.modal?.confirm({ + title: t('translate.confirm.title'), + content: t('translate.confirm.content'), + centered: true + }) + if (!confirmed) return + } + + if (!translateModel) { + window.message.error({ + content: t('translate.error.not_configured'), + key: 'translate-message' + }) + return + } + + if (isMounted.current) { + setIsTranslating(true) + } + + try { + const assistant = getDefaultTranslateAssistant(targetLanguage, textValue) + const translatedText = await fetchTranslate({ content: textValue, assistant }) + if (isMounted.current) { + setTextValue(translatedText) + } + } catch (error) { + console.error('Translation failed:', error) + window.message.error({ + content: t('translate.error.failed'), + key: 'translate-message' + }) + } finally { + if (isMounted.current) { + setIsTranslating(false) + } + } + } + TextEditPopup.hide = onCancel return ( @@ -78,16 +137,24 @@ const PopupContainer: React.FC = ({ text, textareaProps, modalProps, reso afterClose={onClose} afterOpenChange={handleAfterOpenChange} centered> -