diff --git a/src/renderer/src/pages/settings/ModelSettings/ModelSettings.tsx b/src/renderer/src/pages/settings/ModelSettings/ModelSettings.tsx index 1dfc65d93c..8832afda61 100644 --- a/src/renderer/src/pages/settings/ModelSettings/ModelSettings.tsx +++ b/src/renderer/src/pages/settings/ModelSettings/ModelSettings.tsx @@ -1,7 +1,6 @@ import { RedoOutlined } from '@ant-design/icons' import { HStack } from '@renderer/components/Layout' import ModelSelector from '@renderer/components/ModelSelector' -import PromptPopup from '@renderer/components/Popups/PromptPopup' import { isEmbeddingModel, isRerankModel, isTextToImageModel } from '@renderer/config/models' import { TRANSLATE_PROMPT } from '@renderer/config/prompts' import { useTheme } from '@renderer/context/ThemeProvider' @@ -19,6 +18,7 @@ import { FC, useCallback, useMemo } from 'react' import { useTranslation } from 'react-i18next' import { SettingContainer, SettingDescription, SettingGroup, SettingTitle } from '..' +import TranslateSettingsPopup from '../TranslateSettingsPopup/TranslateSettingsPopup' import DefaultAssistantSettings from './DefaultAssistantSettings' import TopicNamingModalPopup from './TopicNamingModalPopup' @@ -53,21 +53,6 @@ const ModelSettings: FC = () => { [translateModel] ) - const onUpdateTranslateModel = async () => { - const prompt = await PromptPopup.show({ - title: t('settings.models.translate_model_prompt_title'), - message: t('settings.models.translate_model_prompt_message'), - defaultValue: translateModelPrompt, - inputProps: { - rows: 10, - onPressEnter: () => {} - } - }) - if (prompt) { - dispatch(setTranslateModelPrompt(prompt)) - } - } - const onResetTranslatePrompt = () => { dispatch(setTranslateModelPrompt(TRANSLATE_PROMPT)) } @@ -133,7 +118,11 @@ const ModelSettings: FC = () => { onChange={(value) => setTranslateModel(find(allModels, JSON.parse(value)) as Model)} placeholder={t('settings.models.empty')} /> - } style={{ marginLeft: 8 }} onClick={onUpdateTranslateModel} /> + } + style={{ marginLeft: 8 }} + onClick={() => TranslateSettingsPopup.show()} + /> {translateModelPrompt !== TRANSLATE_PROMPT && ( } style={{ marginLeft: 8 }} onClick={onResetTranslatePrompt}> diff --git a/src/renderer/src/pages/settings/SettingsPage.tsx b/src/renderer/src/pages/settings/SettingsPage.tsx index e6989aa495..72ff047882 100644 --- a/src/renderer/src/pages/settings/SettingsPage.tsx +++ b/src/renderer/src/pages/settings/SettingsPage.tsx @@ -7,7 +7,6 @@ import { FolderCog, HardDrive, Info, - Languages, MonitorCog, Package, PictureInPicture2, @@ -31,7 +30,6 @@ import QuickAssistantSettings from './QuickAssistantSettings' import SelectionAssistantSettings from './SelectionAssistantSettings/SelectionAssistantSettings' import ShortcutSettings from './ShortcutSettings' import ToolSettings from './ToolSettings' -import TranslateSettings from './TranslateSettings/TranslateSettings' const SettingsPage: FC = () => { const { pathname } = useLocation() @@ -82,12 +80,6 @@ const SettingsPage: FC = () => { {t('settings.mcp.title')} - - - - {t('settings.translate.title')} - - @@ -131,7 +123,6 @@ const SettingsPage: FC = () => { } /> } /> } /> - } /> } /> } /> } /> diff --git a/src/renderer/src/pages/settings/TranslateSettings/TranslateModelSettings.tsx b/src/renderer/src/pages/settings/TranslateSettings/TranslateModelSettings.tsx deleted file mode 100644 index e25b63b040..0000000000 --- a/src/renderer/src/pages/settings/TranslateSettings/TranslateModelSettings.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { HStack } from '@renderer/components/Layout' -import ModelSelector from '@renderer/components/ModelSelector' -import { isEmbeddingModel, isRerankModel, isTextToImageModel } from '@renderer/config/models' -import { useTheme } from '@renderer/context/ThemeProvider' -import { useDefaultModel } from '@renderer/hooks/useAssistant' -import { useProviders } from '@renderer/hooks/useProvider' -import { getModelUniqId, hasModel } from '@renderer/services/ModelService' -import { Model } from '@renderer/types' -import { find } from 'lodash' -import { useCallback, useMemo } from 'react' -import { useTranslation } from 'react-i18next' - -import { SettingDescription, SettingGroup, SettingTitle } from '..' - -const TranslateModelSettings = () => { - const { t } = useTranslation() - const { theme } = useTheme() - const { providers } = useProviders() - const { translateModel, setTranslateModel } = useDefaultModel() - - const allModels = useMemo(() => providers.map((p) => p.models).flat(), [providers]) - - const modelPredicate = useCallback( - (m: Model) => !isEmbeddingModel(m) && !isRerankModel(m) && !isTextToImageModel(m), - [] - ) - - const defaultTranslateModel = useMemo( - () => (hasModel(translateModel) ? getModelUniqId(translateModel) : undefined), - [translateModel] - ) - - return ( - - - - {t('settings.models.translate_model')} - - - - setTranslateModel(find(allModels, JSON.parse(value)) as Model)} - placeholder={t('settings.models.empty')} - /> - - {t('settings.models.translate_model_description')} - - ) -} - -export default TranslateModelSettings diff --git a/src/renderer/src/pages/settings/TranslateSettings/TranslateSettings.tsx b/src/renderer/src/pages/settings/TranslateSettings/TranslateSettings.tsx deleted file mode 100644 index 43c19fd349..0000000000 --- a/src/renderer/src/pages/settings/TranslateSettings/TranslateSettings.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import SvgSpinners180Ring from '@renderer/components/Icons/SvgSpinners180Ring' -import { useTheme } from '@renderer/context/ThemeProvider' -import CustomLanguageSettings from '@renderer/pages/settings/TranslateSettings/CustomLanguageSettings' -import { getAllCustomLanguages } from '@renderer/services/TranslateService' -import { CustomTranslateLanguage } from '@renderer/types' -import { Suspense, useEffect, useState } from 'react' - -import { SettingContainer, SettingGroup } from '..' -import TranslateModelSettings from './TranslateModelSettings' -import TranslatePromptSettings from './TranslatePromptSettings' - -const TranslateSettings = () => { - const { theme } = useTheme() - - const [dataPromise, setDataPromise] = useState>(Promise.resolve([])) - - useEffect(() => { - setDataPromise(getAllCustomLanguages()) - }, []) - - return ( - <> - - - - - }> - - - - - > - ) -} - -const CustomLanguagesSettingsFallback = () => { - return ( - - - - ) -} - -export default TranslateSettings diff --git a/src/renderer/src/pages/settings/TranslateSettings/CustomLanguageModal.tsx b/src/renderer/src/pages/settings/TranslateSettingsPopup/CustomLanguageModal.tsx similarity index 99% rename from src/renderer/src/pages/settings/TranslateSettings/CustomLanguageModal.tsx rename to src/renderer/src/pages/settings/TranslateSettingsPopup/CustomLanguageModal.tsx index 4154eae603..3c8ca22baa 100644 --- a/src/renderer/src/pages/settings/TranslateSettings/CustomLanguageModal.tsx +++ b/src/renderer/src/pages/settings/TranslateSettingsPopup/CustomLanguageModal.tsx @@ -98,7 +98,9 @@ const CustomLanguageModal = ({ isOpen, editingCustomLanguage, onAdd, onEdit, onC footer={footer} onCancel={onCancel} maskClosable={false} + transitionName="animation-move-down" forceRender + centered styles={{ body: { padding: '20px' diff --git a/src/renderer/src/pages/settings/TranslateSettings/CustomLanguageSettings.tsx b/src/renderer/src/pages/settings/TranslateSettingsPopup/CustomLanguageSettings.tsx similarity index 84% rename from src/renderer/src/pages/settings/TranslateSettings/CustomLanguageSettings.tsx rename to src/renderer/src/pages/settings/TranslateSettingsPopup/CustomLanguageSettings.tsx index 0209e5512a..b254b62513 100644 --- a/src/renderer/src/pages/settings/TranslateSettings/CustomLanguageSettings.tsx +++ b/src/renderer/src/pages/settings/TranslateSettingsPopup/CustomLanguageSettings.tsx @@ -1,20 +1,19 @@ import { DeleteOutlined, EditOutlined, PlusOutlined } from '@ant-design/icons' +import { loggerService } from '@logger' import { HStack } from '@renderer/components/Layout' -import { deleteCustomLanguage } from '@renderer/services/TranslateService' +import { deleteCustomLanguage, getAllCustomLanguages } from '@renderer/services/TranslateService' import { CustomTranslateLanguage } from '@renderer/types' import { Button, Popconfirm, Space, Table, TableProps } from 'antd' -import { memo, startTransition, use, useCallback, useEffect, useMemo, useState } from 'react' +import { memo, startTransition, useCallback, useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import styled from 'styled-components' import { SettingRowTitle } from '..' import CustomLanguageModal from './CustomLanguageModal' -type Props = { - dataPromise: Promise -} +const logger = loggerService.withContext('CustomLanguageSettings') -const CustomLanguageSettings = ({ dataPromise }: Props) => { +const CustomLanguageSettings = () => { const { t } = useTranslation() const [displayedItems, setDisplayedItems] = useState([]) const [isModalOpen, setIsModalOpen] = useState(false) @@ -104,18 +103,28 @@ const CustomLanguageSettings = ({ dataPromise }: Props) => { [onDelete, t] ) - const data = use(dataPromise) - useEffect(() => { - setDisplayedItems(data) - }, [data]) + const loadData = async () => { + try { + const data = await getAllCustomLanguages() + setDisplayedItems(data) + } catch (error) { + logger.error('Failed to load custom languages:', error as Error) + } + } + loadData() + }, []) return ( <> {t('translate.custom.label')} - } onClick={onClickAdd}> + } + onClick={onClickAdd} + style={{ marginBottom: 5, marginTop: -5 }}> {t('common.add')} diff --git a/src/renderer/src/pages/settings/TranslateSettings/TranslatePromptSettings.tsx b/src/renderer/src/pages/settings/TranslateSettingsPopup/TranslatePromptSettings.tsx similarity index 98% rename from src/renderer/src/pages/settings/TranslateSettings/TranslatePromptSettings.tsx rename to src/renderer/src/pages/settings/TranslateSettingsPopup/TranslatePromptSettings.tsx index 220feeb6dc..d4b040ddfe 100644 --- a/src/renderer/src/pages/settings/TranslateSettings/TranslatePromptSettings.tsx +++ b/src/renderer/src/pages/settings/TranslateSettingsPopup/TranslatePromptSettings.tsx @@ -45,7 +45,8 @@ const TranslatePromptSettings = () => { onChange={(e) => setLocalPrompt(e.target.value)} onBlur={(e) => dispatch(setTranslateModelPrompt(e.target.value))} autoSize={{ minRows: 4, maxRows: 10 }} - placeholder={t('settings.models.translate_model_prompt_message')}> + placeholder={t('settings.models.translate_model_prompt_message')} + /> ) } diff --git a/src/renderer/src/pages/settings/TranslateSettingsPopup/TranslateSettingsPopup.tsx b/src/renderer/src/pages/settings/TranslateSettingsPopup/TranslateSettingsPopup.tsx new file mode 100644 index 0000000000..6908d89ab3 --- /dev/null +++ b/src/renderer/src/pages/settings/TranslateSettingsPopup/TranslateSettingsPopup.tsx @@ -0,0 +1,74 @@ +import { TopView } from '@renderer/components/TopView' +import { useTheme } from '@renderer/context/ThemeProvider' +import { Modal } from 'antd' +import { useState } from 'react' +import { useTranslation } from 'react-i18next' + +import { SettingContainer, SettingGroup } from '..' +import CustomLanguageSettings from './CustomLanguageSettings' +import TranslatePromptSettings from './TranslatePromptSettings' + +interface Props { + resolve: (data: any) => void +} + +const PopupContainer: React.FC = ({ resolve }) => { + const [open, setOpen] = useState(true) + const { theme } = useTheme() + const { t } = useTranslation() + + const onOk = () => { + setOpen(false) + } + + const onCancel = () => { + setOpen(false) + } + + const onClose = () => { + resolve({}) + } + + TranslateSettingsPopup.hide = onCancel + + return ( + + + + + + + + + ) +} + +const TopViewKey = 'TranslateSettingsPopup' + +export default class TranslateSettingsPopup { + static topviewId = 0 + static hide() { + TopView.hide(TopViewKey) + } + static show() { + return new Promise((resolve) => { + TopView.show( + { + resolve(v) + TopView.hide(TopViewKey) + }} + />, + TopViewKey + ) + }) + } +} diff --git a/src/renderer/src/pages/translate/TranslateSettings.tsx b/src/renderer/src/pages/translate/TranslateSettings.tsx index 2d6897823a..56e793129b 100644 --- a/src/renderer/src/pages/translate/TranslateSettings.tsx +++ b/src/renderer/src/pages/translate/TranslateSettings.tsx @@ -1,18 +1,14 @@ -import { RedoOutlined } from '@ant-design/icons' import LanguageSelect from '@renderer/components/LanguageSelect' import { HStack } from '@renderer/components/Layout' -import { TRANSLATE_PROMPT } from '@renderer/config/prompts' import db from '@renderer/databases' -import { useSettings } from '@renderer/hooks/useSettings' import useTranslate from '@renderer/hooks/useTranslate' -import { useAppDispatch } from '@renderer/store' -import { setTranslateModelPrompt } from '@renderer/store/settings' import { Model, TranslateLanguage } from '@renderer/types' -import { Button, Flex, Input, Modal, Space, Switch, Tooltip } from 'antd' -import { ChevronDown, HelpCircle } from 'lucide-react' +import { Button, Flex, Modal, Space, Switch, Tooltip } from 'antd' +import { HelpCircle } from 'lucide-react' import { FC, memo, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' -import styled from 'styled-components' + +import TranslateSettingsPopup from '../settings/TranslateSettingsPopup/TranslateSettingsPopup' const TranslateSettings: FC<{ visible: boolean @@ -39,37 +35,16 @@ const TranslateSettings: FC<{ setBidirectionalPair }) => { const { t } = useTranslation() - const { translateModelPrompt } = useSettings() - const dispatch = useAppDispatch() const [localPair, setLocalPair] = useState<[TranslateLanguage, TranslateLanguage]>(bidirectionalPair) - const [showPrompt, setShowPrompt] = useState(false) - const [localPrompt, setLocalPrompt] = useState(translateModelPrompt) const { getLanguageByLangcode } = useTranslate() useEffect(() => { setLocalPair(bidirectionalPair) - setLocalPrompt(translateModelPrompt) - }, [bidirectionalPair, translateModelPrompt, visible]) + }, [bidirectionalPair, visible]) - const handleSave = () => { - if (localPair[0] === localPair[1]) { - window.message.warning({ - content: t('translate.language.same'), - key: 'translate-message' - }) - return - } - setBidirectionalPair(localPair) - db.settings.put({ id: 'translate:bidirectional:pair', value: [localPair[0].langCode, localPair[1].langCode] }) - db.settings.put({ id: 'translate:scroll:sync', value: isScrollSyncEnabled }) - db.settings.put({ id: 'translate:markdown:enabled', value: enableMarkdown }) - db.settings.put({ id: 'translate:model:prompt', value: localPrompt }) - dispatch(setTranslateModelPrompt(localPrompt)) - window.message.success({ - content: t('message.save.success.title'), - key: 'translate-settings-save' - }) + const onMoreSetting = () => { onClose() + TranslateSettingsPopup.show() } return ( @@ -78,27 +53,33 @@ const TranslateSettings: FC<{ open={visible} onCancel={onClose} centered={true} - footer={[ - - {t('common.cancel')} - , - - {t('common.save')} - - ]} - width={420}> - + footer={null} + width={420} + transitionName="animation-move-down"> + {t('translate.settings.preview')} - + { + setEnableMarkdown(checked) + db.settings.put({ id: 'translate:markdown:enabled', value: checked }) + }} + /> {t('translate.settings.scroll_sync')} - + { + setIsScrollSyncEnabled(checked) + db.settings.put({ id: 'translate:scroll:sync', value: checked }) + }} + /> @@ -114,7 +95,13 @@ const TranslateSettings: FC<{ - + { + setIsBidirectional(checked) + // 双向翻译设置不需要持久化,它只是界面状态 + }} + /> {isBidirectional && ( @@ -122,78 +109,52 @@ const TranslateSettings: FC<{ setLocalPair([getLanguageByLangcode(value), localPair[1]])} + onChange={(value) => { + const newPair: [TranslateLanguage, TranslateLanguage] = [getLanguageByLangcode(value), localPair[1]] + if (newPair[0] === newPair[1]) { + window.message.warning({ + content: t('translate.language.same'), + key: 'translate-message' + }) + return + } + setLocalPair(newPair) + setBidirectionalPair(newPair) + db.settings.put({ + id: 'translate:bidirectional:pair', + value: [newPair[0].langCode, newPair[1].langCode] + }) + }} /> ⇆ setLocalPair([localPair[0], getLanguageByLangcode(value)])} + onChange={(value) => { + const newPair: [TranslateLanguage, TranslateLanguage] = [localPair[0], getLanguageByLangcode(value)] + if (newPair[0] === newPair[1]) { + window.message.warning({ + content: t('translate.language.same'), + key: 'translate-message' + }) + return + } + setLocalPair(newPair) + setBidirectionalPair(newPair) + db.settings.put({ + id: 'translate:bidirectional:pair', + value: [newPair[0].langCode, newPair[1].langCode] + }) + }} /> )} - - - - setShowPrompt(!showPrompt)}> - {t('settings.models.translate_model_prompt_title')} - - - {localPrompt !== TRANSLATE_PROMPT && ( - - } - size="small" - type="text" - onClick={() => setLocalPrompt(TRANSLATE_PROMPT)} - /> - - )} - - - - - setLocalPrompt(e.target.value)} - placeholder={t('settings.models.translate_model_prompt_message')} - style={{ borderRadius: '8px' }} - /> - + {t('settings.moresetting.label')} ) } export default memo(TranslateSettings) - -const Textarea = styled(Input.TextArea)` - display: flex; - flex: 1; - font-size: 16px; - border-radius: 0; - .ant-input { - resize: none; - padding: 5px 16px; - } - .ant-input-clear-icon { - font-size: 16px; - } -`