From 02930a27932e7d58422a4cad11d7d9e1ed5a8839 Mon Sep 17 00:00:00 2001 From: Teo Date: Wed, 19 Feb 2025 18:50:09 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E7=BF=BB=E8=AF=91?= =?UTF-8?q?=E5=8E=86=E5=8F=B2=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/renderer/src/databases/index.ts | 11 +- src/renderer/src/i18n/locales/en-us.json | 9 +- src/renderer/src/i18n/locales/zh-cn.json | 9 +- .../src/pages/translate/TranslatePage.tsx | 213 +++++++++++++++++- src/renderer/src/types/index.ts | 9 + 5 files changed, 239 insertions(+), 12 deletions(-) diff --git a/src/renderer/src/databases/index.ts b/src/renderer/src/databases/index.ts index cf90000f..8d54c840 100644 --- a/src/renderer/src/databases/index.ts +++ b/src/renderer/src/databases/index.ts @@ -1,4 +1,4 @@ -import { FileType, KnowledgeItem, Topic } from '@renderer/types' +import { FileType, KnowledgeItem, Topic, TranslateHistory } from '@renderer/types' import { Dexie, type EntityTable } from 'dexie' // Database declaration (move this to its own module also) @@ -7,6 +7,7 @@ export const db = new Dexie('CherryStudio') as Dexie & { topics: EntityTable, 'id'> settings: EntityTable<{ id: string; value: any }, 'id'> knowledge_notes: EntityTable + translate_history: EntityTable } db.version(1).stores({ @@ -26,4 +27,12 @@ db.version(3).stores({ knowledge_notes: '&id, baseId, type, content, created_at, updated_at' }) +db.version(4).stores({ + files: 'id, name, origin_name, path, size, ext, type, created_at, count', + topics: '&id, messages', + settings: '&id, value', + knowledge_notes: '&id, baseId, type, content, created_at, updated_at', + translate_history: '&id, sourceText, targetText, sourceLanguage, targetLanguage, createdAt' +}) + export default db diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index 972e22df..b17e4ef1 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -786,7 +786,14 @@ "input.placeholder": "Enter text to translate", "output.placeholder": "Translation", "processing": "Translation in progress...", - "title": "Translation" + "title": "Translation", + "history": { + "title": "Translation History", + "empty": "No translation history", + "clear": "Clear History", + "delete": "Delete", + "clear_description": "Clear history will delete all translation history, continue?" + } }, "tray": { "quit": "Quit", diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index c6586dc7..9bcb8546 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -790,7 +790,14 @@ "input.placeholder": "输入文本进行翻译", "output.placeholder": "翻译", "processing": "翻译中...", - "title": "翻译" + "title": "翻译", + "history": { + "title": "翻译历史", + "empty": "暂无翻译历史", + "clear": "清空历史", + "delete": "删除", + "clear_description": "清空历史将删除所有翻译历史记录,是否继续?" + } }, "tray": { "quit": "退出", diff --git a/src/renderer/src/pages/translate/TranslatePage.tsx b/src/renderer/src/pages/translate/TranslatePage.tsx index 7bcda1f7..4df3821a 100644 --- a/src/renderer/src/pages/translate/TranslatePage.tsx +++ b/src/renderer/src/pages/translate/TranslatePage.tsx @@ -1,4 +1,12 @@ -import { CheckOutlined, SendOutlined, SettingOutlined, SwapOutlined, WarningOutlined } from '@ant-design/icons' +import { + CheckOutlined, + DeleteOutlined, + HistoryOutlined, + SendOutlined, + SettingOutlined, + SwapOutlined, + WarningOutlined +} from '@ant-design/icons' import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar' import CopyIcon from '@renderer/components/Icons/CopyIcon' import { isLocalAi } from '@renderer/config/env' @@ -7,10 +15,12 @@ import db from '@renderer/databases' import { useDefaultModel } from '@renderer/hooks/useAssistant' import { fetchTranslate } from '@renderer/services/ApiService' import { getDefaultTranslateAssistant } from '@renderer/services/AssistantService' -import { Assistant, Message } from '@renderer/types' +import { Assistant, Message, TranslateHistory } from '@renderer/types' import { runAsyncFunction, uuid } from '@renderer/utils' -import { Button, Flex, Select, Space } from 'antd' +import { Button, Dropdown, Empty, Flex, Popconfirm, Select, Space } from 'antd' import TextArea, { TextAreaRef } from 'antd/es/input/TextArea' +import dayjs from 'dayjs' +import { useLiveQuery } from 'dexie-react-hooks' import { isEmpty } from 'lodash' import { FC, useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' @@ -29,13 +39,42 @@ const TranslatePage: FC = () => { const { translateModel } = useDefaultModel() const [loading, setLoading] = useState(false) const [copied, setCopied] = useState(false) + const [historyDrawerVisible, setHistoryDrawerVisible] = useState(false) const contentContainerRef = useRef(null) const textAreaRef = useRef(null) + const translateHistory = useLiveQuery(() => db.translate_history.orderBy('createdAt').reverse().toArray(), []) + _text = text _result = result _targetLanguage = targetLanguage + const saveTranslateHistory = async ( + sourceText: string, + targetText: string, + sourceLanguage: string, + targetLanguage: string + ) => { + const history: TranslateHistory = { + id: uuid(), + sourceText, + targetText, + sourceLanguage, + targetLanguage, + createdAt: new Date().toISOString() + } + console.log('🌟TEO🌟 ~ saveTranslateHistory ~ history:', history) + await db.translate_history.add(history) + } + + const deleteHistory = async (id: string) => { + db.translate_history.delete(id) + } + + const clearHistory = async () => { + db.translate_history.clear() + } + const onTranslate = async () => { if (!text.trim()) { return @@ -64,7 +103,17 @@ const TranslatePage: FC = () => { } setLoading(true) - await fetchTranslate({ message, assistant, onResponse: (text) => setResult(text) }) + let translatedText = '' + await fetchTranslate({ + message, + assistant, + onResponse: (text) => { + translatedText = text + setResult(text) + } + }) + + await saveTranslateHistory(text, translatedText, 'any', targetLanguage) setLoading(false) } @@ -74,6 +123,12 @@ const TranslatePage: FC = () => { setTimeout(() => setCopied(false), 2000) } + const onHistoryItemClick = (history: TranslateHistory) => { + setText(history.sourceText) + setResult(history.targetText) + setTargetLanguage(history.targetLanguage) + } + useEffect(() => { isEmpty(text) && setResult('') }, [text]) @@ -113,9 +168,69 @@ const TranslatePage: FC = () => { return ( - {t('translate.title')} + + {t('translate.title')} + + + )} + + {translateHistory && translateHistory.length ? ( + + {translateHistory.map((item) => ( + , + danger: true, + onClick: () => deleteHistory(item.id) + } + ] + }}> + onHistoryItemClick(item)}> + + {item.sourceText} + {item.targetText} + {dayjs(item.createdAt).format('MM/DD HH:mm')} + + + + ))} + + ) : ( + + + + )} + + @@ -153,7 +268,7 @@ const TranslatePage: FC = () => { - + @@ -195,12 +310,13 @@ const Container = styled.div` flex: 1; ` -const ContentContainer = styled.div` +const ContentContainer = styled.div<{ $historyDrawerVisible: boolean }>` height: calc(100vh - var(--navbar-height)); display: grid; - grid-template-columns: 1fr 40px 1fr; + grid-template-columns: auto 1fr 40px 1fr; flex: 1; padding: 20px; + position: relative; ` const InputContainer = styled.div` @@ -257,4 +373,83 @@ const TranslateButton = styled(Button)`` const CopyButton = styled(Button)`` +const HistoryContainner = styled.div<{ $historyDrawerVisible: boolean }>` + width: ${({ $historyDrawerVisible }) => ($historyDrawerVisible ? '300px' : '0')}; + height: calc(100vh - var(--navbar-height) - 40px); + transition: + width 0.2s, + opacity 0.2s; + border: 1px solid var(--color-border-soft); + border-radius: 10px; + margin-right: 20px; + display: flex; + flex-direction: column; + overflow: hidden; + padding-right: 2px; + padding-bottom: 5px; + + ${({ $historyDrawerVisible }) => + !$historyDrawerVisible && + ` + border: none; + margin-right: 0; + opacity: 0; + `} +` + +const HistoryList = styled.div` + flex: 1; + display: flex; + flex-direction: column; + gap: 16px; + overflow-y: auto; + padding: 0 5px; +` + +const HistoryListItem = styled.div` + width: 100%; + padding: 5px 10px; + border-radius: var(--list-item-border-radius); + cursor: pointer; + transition: background-color 0.2s; + position: relative; + + button { + opacity: 0; + transition: opacity 0.2s; + } + + &:hover { + background-color: var(--color-background-mute); + button { + opacity: 1; + } + } + + &:not(:last-child)::after { + content: ''; + display: block; + width: 100%; + height: 1px; + border-bottom: 1px dashed var(--color-border-soft); + position: absolute; + bottom: -8px; + left: 0; + } +` + +const HistoryListItemTitle = styled.div` + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + text-overflow: ellipsis; + font-size: 13px; +` + +const HistoryListItemDate = styled.div` + font-size: 12px; + color: var(--color-text-3); +` + export default TranslatePage diff --git a/src/renderer/src/types/index.ts b/src/renderer/src/types/index.ts index bf9d83c2..fc81352e 100644 --- a/src/renderer/src/types/index.ts +++ b/src/renderer/src/types/index.ts @@ -271,4 +271,13 @@ export type GenerateImageParams = { promptEnhancement?: boolean } +export interface TranslateHistory { + id: string + sourceText: string + targetText: string + sourceLanguage: string + targetLanguage: string + createdAt: string +} + export type SidebarIcon = 'assistants' | 'agents' | 'paintings' | 'translate' | 'minapp' | 'knowledge' | 'files'