From 75d2eacf7c23d231b8f3869decf2db1f4c6f5e0b Mon Sep 17 00:00:00 2001 From: fullex <0xfullex@gmail.com> Date: Thu, 29 May 2025 19:26:44 +0800 Subject: [PATCH] feat: add "Regenerate" in action window --- src/renderer/src/i18n/locales/en-us.json | 7 +- src/renderer/src/i18n/locales/ja-jp.json | 3 +- src/renderer/src/i18n/locales/ru-ru.json | 3 +- src/renderer/src/i18n/locales/zh-cn.json | 3 +- src/renderer/src/i18n/locales/zh-tw.json | 3 +- .../action/components/ActionGeneral.tsx | 10 ++- .../action/components/ActionTranslate.tsx | 2 +- .../action/components/WindowFooter.tsx | 67 ++++++++++++++++--- 8 files changed, 80 insertions(+), 18 deletions(-) diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index 1c18afc63a..1f33bd0dab 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -1849,9 +1849,10 @@ "original_show": "Show Original", "original_hide": "Hide Original", "original_copy": "Copy Original", - "esc_close": "Esc to Close", - "esc_stop": "Esc to Stop", - "c_copy": "C to Copy" + "esc_close": "Esc: Close", + "esc_stop": "Esc: Stop", + "c_copy": "C: Copy", + "r_regenerate": "R: Regenerate" } }, "settings": { diff --git a/src/renderer/src/i18n/locales/ja-jp.json b/src/renderer/src/i18n/locales/ja-jp.json index aece8cd3d7..9b3e87250a 100644 --- a/src/renderer/src/i18n/locales/ja-jp.json +++ b/src/renderer/src/i18n/locales/ja-jp.json @@ -1851,7 +1851,8 @@ "original_copy": "原文をコピー", "esc_close": "Escで閉じる", "esc_stop": "Escで停止", - "c_copy": "Cでコピー" + "c_copy": "Cでコピー", + "r_regenerate": "Rで再生成" } }, "settings": { diff --git a/src/renderer/src/i18n/locales/ru-ru.json b/src/renderer/src/i18n/locales/ru-ru.json index 13c1fbb3ac..e57faf915d 100644 --- a/src/renderer/src/i18n/locales/ru-ru.json +++ b/src/renderer/src/i18n/locales/ru-ru.json @@ -1851,7 +1851,8 @@ "original_copy": "Копировать оригинал", "esc_close": "Esc - закрыть", "esc_stop": "Esc - остановить", - "c_copy": "C - копировать" + "c_copy": "C - копировать", + "r_regenerate": "R - перегенерировать" } }, "settings": { diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index 9519427dae..eb5c11de7b 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -1851,7 +1851,8 @@ "original_copy": "复制原文", "esc_close": "Esc 关闭", "esc_stop": "Esc 停止", - "c_copy": "C 复制" + "c_copy": "C 复制", + "r_regenerate": "R 重新生成" } }, "settings": { diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index d703265a2c..e5df641063 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -1851,7 +1851,8 @@ "original_copy": "複製原文", "esc_close": "Esc 關閉", "esc_stop": "Esc 停止", - "c_copy": "C 複製" + "c_copy": "C 複製", + "r_regenerate": "R 重新生成" } }, "settings": { diff --git a/src/renderer/src/windows/selection/action/components/ActionGeneral.tsx b/src/renderer/src/windows/selection/action/components/ActionGeneral.tsx index 58aa0a60c9..90cce65f14 100644 --- a/src/renderer/src/windows/selection/action/components/ActionGeneral.tsx +++ b/src/renderer/src/windows/selection/action/components/ActionGeneral.tsx @@ -220,6 +220,12 @@ const ActionGeneral: FC = React.memo(({ action, scrollToBottom }) => { } } + const handleRegenerate = () => { + setContentToCopy('') + setIsLoading(true) + fetchResult() + } + return ( <> @@ -250,7 +256,7 @@ const ActionGeneral: FC = React.memo(({ action, scrollToBottom }) => { {error && {error}} - + ) }) @@ -315,7 +321,7 @@ const OriginalContentCopyWrapper = styled.div` ` const FooterPadding = styled.div` - min-height: 32px; + min-height: 12px; ` const ErrorMsg = styled.div` diff --git a/src/renderer/src/windows/selection/action/components/ActionTranslate.tsx b/src/renderer/src/windows/selection/action/components/ActionTranslate.tsx index d115872f77..1d36c18768 100644 --- a/src/renderer/src/windows/selection/action/components/ActionTranslate.tsx +++ b/src/renderer/src/windows/selection/action/components/ActionTranslate.tsx @@ -204,7 +204,7 @@ const OriginalContentCopyWrapper = styled.div` ` const FooterPadding = styled.div` - min-height: 32px; + min-height: 12px; ` const ErrorMsg = styled.div` diff --git a/src/renderer/src/windows/selection/action/components/WindowFooter.tsx b/src/renderer/src/windows/selection/action/components/WindowFooter.tsx index b2f01711df..78e2979786 100644 --- a/src/renderer/src/windows/selection/action/components/WindowFooter.tsx +++ b/src/renderer/src/windows/selection/action/components/WindowFooter.tsx @@ -1,5 +1,5 @@ import { LoadingOutlined } from '@ant-design/icons' -import { CircleX, Copy, Pause } from 'lucide-react' +import { CircleX, Copy, Pause, RefreshCw } from 'lucide-react' import { FC, useEffect, useState } from 'react' import { useHotkeys } from 'react-hotkeys-hook' import { useTranslation } from 'react-i18next' @@ -8,14 +8,22 @@ interface FooterProps { content?: string loading?: boolean onPause?: () => void + onRegenerate?: () => void } -const WindowFooter: FC = ({ content = '', loading = false, onPause = () => {} }) => { +const WindowFooter: FC = ({ + content = '', + loading = false, + onPause = undefined, + onRegenerate = undefined +}) => { const { t } = useTranslation() const [isWindowFocus, setIsWindowFocus] = useState(true) const [isCopyHovered, setIsCopyHovered] = useState(false) const [isEscHovered, setIsEscHovered] = useState(false) + const [isRegenerateHovered, setIsRegenerateHovered] = useState(false) + const [isContainerHovered, setIsContainerHovered] = useState(false) useEffect(() => { window.addEventListener('focus', handleWindowFocus) @@ -31,6 +39,10 @@ const WindowFooter: FC = ({ content = '', loading = false, onPause handleCopy() }) + useHotkeys('r', () => { + handleRegenerate() + }) + useHotkeys('esc', () => { handleEsc() }) @@ -48,8 +60,26 @@ const WindowFooter: FC = ({ content = '', loading = false, onPause } } + const handleRegenerate = () => { + setIsRegenerateHovered(true) + setTimeout(() => { + setIsRegenerateHovered(false) + }, 200) + + if (loading && onPause) { + onPause() + } + + if (onRegenerate) { + //wait for a little time + setTimeout(() => { + onRegenerate() + }, 200) + } + } + const handleCopy = () => { - if (!content) return + if (!content || loading) return navigator.clipboard .writeText(content) @@ -74,7 +104,10 @@ const WindowFooter: FC = ({ content = '', loading = false, onPause } return ( - + setIsContainerHovered(true)} + onMouseLeave={() => setIsContainerHovered(false)} + $isHovered={isContainerHovered}> {loading ? ( @@ -96,6 +129,12 @@ const WindowFooter: FC = ({ content = '', loading = false, onPause )} + {onRegenerate && ( + + + {t('selection.action.window.r_regenerate')} + + )} {t('selection.action.window.c_copy')} @@ -105,19 +144,28 @@ const WindowFooter: FC = ({ content = '', loading = false, onPause ) } -const Container = styled.div` +const Container = styled.div<{ $isHovered: boolean }>` position: absolute; bottom: 0; - left: 8px; - right: 8px; + left: 50%; + transform: translateX(-50%); + max-width: 480px; + min-width: min-content; + width: calc(100% - 16px); display: flex; flex-direction: row; align-items: center; justify-content: center; - padding: 5px 0; + padding: 5px 8px; height: 32px; backdrop-filter: blur(8px); border-radius: 8px; + opacity: 0; + transition: all 0.3s ease; + + &:hover { + opacity: 1; + } ` const OpButtonWrapper = styled.div` @@ -144,6 +192,9 @@ const OpButton = styled.div<{ $isWindowFocus: boolean; $isHovered?: boolean }>` opacity: ${(props) => (props.$isWindowFocus ? 1 : 0.2)}; transition: opacity 0.3s ease; transition: color 0.2s ease; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; .btn-icon { color: var(--color-text-secondary);