From a267a8d4c382574aa6c10d435d77bae9855527ed Mon Sep 17 00:00:00 2001 From: kangfenmao Date: Sun, 28 Jul 2024 01:01:15 +0800 Subject: [PATCH] feat: add translation module --- src/renderer/src/App.tsx | 2 + .../src/assets/fonts/icon-fonts/iconfont.css | 47 +++ .../src/assets/fonts/icon-fonts/iconfont.ttf | Bin 0 -> 3992 bytes .../src/assets/fonts/icon-fonts/iconfont.woff | Bin 0 -> 2444 bytes .../assets/fonts/icon-fonts/iconfont.woff2 | Bin 0 -> 1952 bytes src/renderer/src/assets/styles/index.scss | 2 +- src/renderer/src/components/app/Sidebar.tsx | 18 +- src/renderer/src/hooks/useAssistant.ts | 10 +- src/renderer/src/i18n/index.ts | 48 ++- .../src/pages/settings/ModelSettings.tsx | 35 +- .../src/pages/translate/TranslatePage.tsx | 311 ++++++++++++++++++ src/renderer/src/services/ProviderSDK.ts | 24 ++ src/renderer/src/services/api.ts | 39 ++- src/renderer/src/services/assistant.ts | 4 + src/renderer/src/store/llm.ts | 8 +- 15 files changed, 532 insertions(+), 16 deletions(-) create mode 100644 src/renderer/src/assets/fonts/icon-fonts/iconfont.css create mode 100644 src/renderer/src/assets/fonts/icon-fonts/iconfont.ttf create mode 100644 src/renderer/src/assets/fonts/icon-fonts/iconfont.woff create mode 100644 src/renderer/src/assets/fonts/icon-fonts/iconfont.woff2 create mode 100644 src/renderer/src/pages/translate/TranslatePage.tsx diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index 515ea508..4e6ede0f 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -9,6 +9,7 @@ import { AntdThemeConfig, getAntdLocale } from './config/antd' import AppsPage from './pages/apps/AppsPage' import HomePage from './pages/home/HomePage' import SettingsPage from './pages/settings/SettingsPage' +import TranslatePage from './pages/translate/TranslatePage' function App(): JSX.Element { return ( @@ -21,6 +22,7 @@ function App(): JSX.Element { } /> } /> + } /> } /> diff --git a/src/renderer/src/assets/fonts/icon-fonts/iconfont.css b/src/renderer/src/assets/fonts/icon-fonts/iconfont.css new file mode 100644 index 00000000..098b60a9 --- /dev/null +++ b/src/renderer/src/assets/fonts/icon-fonts/iconfont.css @@ -0,0 +1,47 @@ +@font-face { + font-family: "iconfont"; /* Project id 4563475 */ + src: url('iconfont.woff2?t=1722099305424') format('woff2'), + url('iconfont.woff?t=1722099305424') format('woff'), + url('iconfont.ttf?t=1722099305424') format('truetype'); +} + +.iconfont { + font-family: "iconfont" !important; + font-size: 16px; + font-style: normal; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.icon-translate_line:before { + content: "\e7de"; +} + +.icon-history:before { + content: "\e758"; +} + +.icon-hidesidebarhoriz:before { + content: "\e8eb"; +} + +.icon-showsidebarhoriz:before { + content: "\e944"; +} + +.icon-a-addchat:before { + content: "\e658"; +} + +.icon-appstore:before { + content: "\e792"; +} + +.icon-chat:before { + content: "\e615"; +} + +.icon-setting:before { + content: "\e78e"; +} + diff --git a/src/renderer/src/assets/fonts/icon-fonts/iconfont.ttf b/src/renderer/src/assets/fonts/icon-fonts/iconfont.ttf new file mode 100644 index 0000000000000000000000000000000000000000..64e9a4e360b19475e3b6efe0464e752e4ef4b3d2 GIT binary patch literal 3992 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 literal 0 HcmV?d00001 diff --git a/src/renderer/src/assets/fonts/icon-fonts/iconfont.woff2 b/src/renderer/src/assets/fonts/icon-fonts/iconfont.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..c2e0fb69b7ec596bf6d949c15503b142daca9e07 GIT binary patch literal 1952 zcmV;R2VeMiPew8T0RR9100*D|3jhEB01ub|00&V30RR9100000000000000000000 z0000SR0d!GgbE6faE=uLHUcCABnuP(1Rw>3X9s~08)pq85q5)e3PkqLgiMa^OD5MV zCd^#+eo?ICwBcJbl+p*@O?yeuq=r{)Kt*+CkxpC*o5SY7)L|w9OznDRe|H1`ipZrl zoPGDR0lEnpVTN*c2OjN}Quvgb!So!YV`x!&JWo1OOV@Dv|Y#?O242>^E^VB1z!PCxA4a7&&Z6 zKPU_ZBn>nI3bCx~nGz5yAO*<9O-F#B&4p6f&yDhbh;$UOw(C1 zZCJ1|y9$EIDU3kUD3I)SzdYK#}_MhqHS&NR(5*A`51ep*2??! zc`Ud*o|qP5^ZN;@_4#uq)b8^^B6_{kZI45EQh;Zy?kPQWCXAfE1V(m^9yYhNwGF}= z`#w?2Iyjr2cL}PFbN1{w{e}$e$4=R2!oiuB$UQXuV&y$TE(1sA1anTtZdkKn+}NuR zd+{zhWNZvYpDSh`Mg-X$ZG289*uJJ>MXn~AUH8j$126u#UAlov*XbxyvA2Xw5mDE$W25$gpP3q};3W?lBmi3G&<4 z^XtU$Xl`RVpL{w$pFLj47oIFlWe;WwvFoi-ar~AnF6~@Vk#MSjD>zY-A{@wI``f%; z(dV;2e{S{rx$`!^^|{?Arl#_$MJfWb?=YrrHzulI1EuzSbT=oZCRG%kQbVW-Nx%3U zHK8Jkh!!Mye6HnkAli~W==}Hw3mtmUqFYxmK7YKMZt4j}c&D0Rg=HxlECKFpz-$P(>lASh!^~ZGZ7Ofvo@(@%Qnwj@+qCJt zS~=kOmtfDM#mcYs?RQU&Qxu7klQuW_XW*h!Ryf!yX$k0?x8b2&SG8b{dB|0Xl-lLH3Ns6Z)>Z$m>KWf$eyO*ruVd%gw!@){=3XtV@;22% zK^?XYs#j>PIMgew-wubWPwz_=KlF^Z8h%;91F}11W_IceL`mN`CH8+bJ!@*j_OOTm z5O_o#qte2{z<4OM7MGwz@NtdP0Du}t{!w^o{8V-5GUcXPNo9MX)gM(seL~CsIm|zD z!Kb_aW`#420}7CJK$r}POW#w1MJHOq)B{SkTf`htti%>5d?Wzc9RtWz7PWWNiZq>B z@}o$TmXeSo3Mh_f1PO?#rBX1+1sRAHr8LqsoXp2!!a|v z82-cod5mM^mQJb>B2y0^+WoNW2r5&9UUylwp7&XUxnp*o`q72as$V$(_iSD~4RK)2 z|Gh6v*UoZB?4OQBVTk2}qn$$s?5AMp)Zs>Q<(5uV=pqoXfIb#|m^$p-0D;Qy)}Ys& zJiu4a_F|yeRj^xL)W2u}-d(58bClz&X}f!RJAM5^l { const { pathname } = useLocation() @@ -29,6 +30,11 @@ const Sidebar: FC = () => { + + + + + @@ -85,22 +91,28 @@ const Icon = styled.div` margin-bottom: 5px; transition: background-color 0.2s ease; -webkit-app-region: none; - .iconfont { + .iconfont, + .anticon { color: var(--color-icon); font-size: 20px; transition: color 0.2s ease; text-decoration: none; } + .anticon { + font-size: 17px; + } &:hover { background-color: #ffffff30; cursor: pointer; - .iconfont { + .iconfont, + .anticon { color: var(--color-icon-white); } } &.active { background-color: #ffffff20; - .iconfont { + .iconfont, + .anticon { color: var(--color-icon-white); } } diff --git a/src/renderer/src/hooks/useAssistant.ts b/src/renderer/src/hooks/useAssistant.ts index f923910d..817a9835 100644 --- a/src/renderer/src/hooks/useAssistant.ts +++ b/src/renderer/src/hooks/useAssistant.ts @@ -14,7 +14,7 @@ import { updateTopic, updateTopics } from '@renderer/store/assistants' -import { setDefaultModel as _setDefaultModel, setTopicNamingModel as _setTopicNamingModel } from '@renderer/store/llm' +import { setDefaultModel, setTopicNamingModel, setTranslateModel } from '@renderer/store/llm' import { Assistant, AssistantSettings, Model, Topic } from '@renderer/types' import localforage from 'localforage' @@ -71,13 +71,15 @@ export function useDefaultAssistant() { } export function useDefaultModel() { - const { defaultModel, topicNamingModel } = useAppSelector((state) => state.llm) + const { defaultModel, topicNamingModel, translateModel } = useAppSelector((state) => state.llm) const dispatch = useAppDispatch() return { defaultModel, topicNamingModel, - setDefaultModel: (model: Model) => dispatch(_setDefaultModel({ model })), - setTopicNamingModel: (model: Model) => dispatch(_setTopicNamingModel({ model })) + translateModel, + setDefaultModel: (model: Model) => dispatch(setDefaultModel({ model })), + setTopicNamingModel: (model: Model) => dispatch(setTopicNamingModel({ model })), + setTranslateModel: (model: Model) => dispatch(setTranslateModel({ model })) } } diff --git a/src/renderer/src/i18n/index.ts b/src/renderer/src/i18n/index.ts index ab46ca35..0a903840 100644 --- a/src/renderer/src/i18n/index.ts +++ b/src/renderer/src/i18n/index.ts @@ -102,7 +102,7 @@ const resources = { title: 'Settings', general: 'General Settings', provider: 'Model Provider', - model: 'Model Settings', + model: 'Default Model', assistant: 'Default Assistant', about: 'About & Feedback', 'messages.model.title': 'Model Settings', @@ -124,6 +124,7 @@ const resources = { 'provider.api.url.reset': 'Reset', 'models.default_assistant_model': 'Default Assistant Model', 'models.topic_naming_model': 'Topic Naming Model', + 'models.translate_model': 'Translate Model', 'models.add.add_model': 'Add Model', 'models.add.model_id.placeholder': 'Required e.g. gpt-3.5-turbo', 'models.add.model_id': 'Model ID', @@ -156,6 +157,27 @@ const resources = { 'about.contact.title': '📧 Contact', 'about.contact.button': 'Email', 'proxy.title': 'Proxy Address' + }, + translate: { + title: 'Translation', + 'any.language': 'Any language', + 'button.translate': 'Translate', + 'error.not_configured': 'Translation model is not configured', + 'input.placeholder': 'Enter text to translate', + 'output.placeholder': 'Translation' + }, + languages: { + english: 'English', + chinese: 'Chinese', + 'chinese-traditional': 'Traditional Chinese', + japanese: 'Japanese', + korean: 'Korean', + russian: 'Russian', + spanish: 'Spanish', + french: 'French', + italian: 'Italian', + portuguese: 'Portuguese', + arabic: 'Arabic' } } }, @@ -258,7 +280,7 @@ const resources = { title: '设置', general: '常规设置', provider: '模型提供商', - model: '模型设置', + model: '默认模型', assistant: '默认助手', about: '关于我们', 'messages.model.title': '模型设置', @@ -280,6 +302,7 @@ const resources = { 'provider.api.url.reset': '重置', 'models.default_assistant_model': '默认助手模型', 'models.topic_naming_model': '话题命名模型', + 'models.translate_model': '翻译模型', 'models.add.add_model': '添加模型', 'models.add.model_id.placeholder': '必填 例如 gpt-3.5-turbo', 'models.add.model_id': '模型 ID', @@ -312,6 +335,27 @@ const resources = { 'about.contact.title': '📧 邮件联系', 'about.contact.button': '邮件', 'proxy.title': '代理地址' + }, + translate: { + title: '翻译', + 'any.language': '任意语言', + 'button.translate': '翻译', + 'error.not_configured': '翻译模型未配置', + 'input.placeholder': '输入文本进行翻译', + 'output.placeholder': '翻译' + }, + languages: { + english: '英文', + chinese: '简体中文', + 'chinese-traditional': '繁体中文', + japanese: '日文', + korean: '韩文', + russian: '俄文', + spanish: '西班牙文', + french: '法文', + italian: '意大利文', + portuguese: '葡萄牙文', + arabic: '阿拉伯文' } } } diff --git a/src/renderer/src/pages/settings/ModelSettings.tsx b/src/renderer/src/pages/settings/ModelSettings.tsx index 0937a34a..2fa73d6f 100644 --- a/src/renderer/src/pages/settings/ModelSettings.tsx +++ b/src/renderer/src/pages/settings/ModelSettings.tsx @@ -6,9 +6,11 @@ import { useDefaultModel } from '@renderer/hooks/useAssistant' import { find } from 'lodash' import { Model } from '@renderer/types' import { useTranslation } from 'react-i18next' +import { EditOutlined, MessageOutlined, TranslationOutlined } from '@ant-design/icons' const ModelSettings: FC = () => { - const { defaultModel, topicNamingModel, setDefaultModel, setTopicNamingModel } = useDefaultModel() + const { defaultModel, topicNamingModel, translateModel, setDefaultModel, setTopicNamingModel, setTranslateModel } = + useDefaultModel() const { providers } = useProviders() const allModels = providers.map((p) => p.models).flat() const { t } = useTranslation() @@ -24,9 +26,16 @@ const ModelSettings: FC = () => { })) })) + const iconStyle = { fontSize: 16, marginRight: 8 } + return ( - {t('settings.models.default_assistant_model')} + +
+ + {t('settings.models.default_assistant_model')} +
+
{ onChange={(id) => setTopicNamingModel(find(allModels, { id }) as Model)} options={selectOptions} /> +
+ +
+ + {t('settings.models.translate_model')} +
+
+ + + +