feat: add "Regenerate" in action window

This commit is contained in:
fullex 2025-05-29 19:26:44 +08:00 committed by 亢奋猫
parent d18436578d
commit 75d2eacf7c
8 changed files with 80 additions and 18 deletions

View File

@ -1849,9 +1849,10 @@
"original_show": "Show Original", "original_show": "Show Original",
"original_hide": "Hide Original", "original_hide": "Hide Original",
"original_copy": "Copy Original", "original_copy": "Copy Original",
"esc_close": "Esc to Close", "esc_close": "Esc: Close",
"esc_stop": "Esc to Stop", "esc_stop": "Esc: Stop",
"c_copy": "C to Copy" "c_copy": "C: Copy",
"r_regenerate": "R: Regenerate"
} }
}, },
"settings": { "settings": {

View File

@ -1851,7 +1851,8 @@
"original_copy": "原文をコピー", "original_copy": "原文をコピー",
"esc_close": "Escで閉じる", "esc_close": "Escで閉じる",
"esc_stop": "Escで停止", "esc_stop": "Escで停止",
"c_copy": "Cでコピー" "c_copy": "Cでコピー",
"r_regenerate": "Rで再生成"
} }
}, },
"settings": { "settings": {

View File

@ -1851,7 +1851,8 @@
"original_copy": "Копировать оригинал", "original_copy": "Копировать оригинал",
"esc_close": "Esc - закрыть", "esc_close": "Esc - закрыть",
"esc_stop": "Esc - остановить", "esc_stop": "Esc - остановить",
"c_copy": "C - копировать" "c_copy": "C - копировать",
"r_regenerate": "R - перегенерировать"
} }
}, },
"settings": { "settings": {

View File

@ -1851,7 +1851,8 @@
"original_copy": "复制原文", "original_copy": "复制原文",
"esc_close": "Esc 关闭", "esc_close": "Esc 关闭",
"esc_stop": "Esc 停止", "esc_stop": "Esc 停止",
"c_copy": "C 复制" "c_copy": "C 复制",
"r_regenerate": "R 重新生成"
} }
}, },
"settings": { "settings": {

View File

@ -1851,7 +1851,8 @@
"original_copy": "複製原文", "original_copy": "複製原文",
"esc_close": "Esc 關閉", "esc_close": "Esc 關閉",
"esc_stop": "Esc 停止", "esc_stop": "Esc 停止",
"c_copy": "C 複製" "c_copy": "C 複製",
"r_regenerate": "R 重新生成"
} }
}, },
"settings": { "settings": {

View File

@ -220,6 +220,12 @@ const ActionGeneral: FC<Props> = React.memo(({ action, scrollToBottom }) => {
} }
} }
const handleRegenerate = () => {
setContentToCopy('')
setIsLoading(true)
fetchResult()
}
return ( return (
<> <>
<Container> <Container>
@ -250,7 +256,7 @@ const ActionGeneral: FC<Props> = React.memo(({ action, scrollToBottom }) => {
{error && <ErrorMsg>{error}</ErrorMsg>} {error && <ErrorMsg>{error}</ErrorMsg>}
</Container> </Container>
<FooterPadding /> <FooterPadding />
<WindowFooter loading={isLoading} onPause={handlePause} content={contentToCopy} /> <WindowFooter loading={isLoading} onPause={handlePause} onRegenerate={handleRegenerate} content={contentToCopy} />
</> </>
) )
}) })
@ -315,7 +321,7 @@ const OriginalContentCopyWrapper = styled.div`
` `
const FooterPadding = styled.div` const FooterPadding = styled.div`
min-height: 32px; min-height: 12px;
` `
const ErrorMsg = styled.div` const ErrorMsg = styled.div`

View File

@ -204,7 +204,7 @@ const OriginalContentCopyWrapper = styled.div`
` `
const FooterPadding = styled.div` const FooterPadding = styled.div`
min-height: 32px; min-height: 12px;
` `
const ErrorMsg = styled.div` const ErrorMsg = styled.div`

View File

@ -1,5 +1,5 @@
import { LoadingOutlined } from '@ant-design/icons' 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 { FC, useEffect, useState } from 'react'
import { useHotkeys } from 'react-hotkeys-hook' import { useHotkeys } from 'react-hotkeys-hook'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
@ -8,14 +8,22 @@ interface FooterProps {
content?: string content?: string
loading?: boolean loading?: boolean
onPause?: () => void onPause?: () => void
onRegenerate?: () => void
} }
const WindowFooter: FC<FooterProps> = ({ content = '', loading = false, onPause = () => {} }) => { const WindowFooter: FC<FooterProps> = ({
content = '',
loading = false,
onPause = undefined,
onRegenerate = undefined
}) => {
const { t } = useTranslation() const { t } = useTranslation()
const [isWindowFocus, setIsWindowFocus] = useState(true) const [isWindowFocus, setIsWindowFocus] = useState(true)
const [isCopyHovered, setIsCopyHovered] = useState(false) const [isCopyHovered, setIsCopyHovered] = useState(false)
const [isEscHovered, setIsEscHovered] = useState(false) const [isEscHovered, setIsEscHovered] = useState(false)
const [isRegenerateHovered, setIsRegenerateHovered] = useState(false)
const [isContainerHovered, setIsContainerHovered] = useState(false)
useEffect(() => { useEffect(() => {
window.addEventListener('focus', handleWindowFocus) window.addEventListener('focus', handleWindowFocus)
@ -31,6 +39,10 @@ const WindowFooter: FC<FooterProps> = ({ content = '', loading = false, onPause
handleCopy() handleCopy()
}) })
useHotkeys('r', () => {
handleRegenerate()
})
useHotkeys('esc', () => { useHotkeys('esc', () => {
handleEsc() handleEsc()
}) })
@ -48,8 +60,26 @@ const WindowFooter: FC<FooterProps> = ({ 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 = () => { const handleCopy = () => {
if (!content) return if (!content || loading) return
navigator.clipboard navigator.clipboard
.writeText(content) .writeText(content)
@ -74,7 +104,10 @@ const WindowFooter: FC<FooterProps> = ({ content = '', loading = false, onPause
} }
return ( return (
<Container> <Container
onMouseEnter={() => setIsContainerHovered(true)}
onMouseLeave={() => setIsContainerHovered(false)}
$isHovered={isContainerHovered}>
<OpButtonWrapper> <OpButtonWrapper>
<OpButton onClick={handleEsc} $isWindowFocus={isWindowFocus} data-hovered={isEscHovered}> <OpButton onClick={handleEsc} $isWindowFocus={isWindowFocus} data-hovered={isEscHovered}>
{loading ? ( {loading ? (
@ -96,6 +129,12 @@ const WindowFooter: FC<FooterProps> = ({ content = '', loading = false, onPause
</> </>
)} )}
</OpButton> </OpButton>
{onRegenerate && (
<OpButton onClick={handleRegenerate} $isWindowFocus={isWindowFocus} data-hovered={isRegenerateHovered}>
<RefreshCw size={14} className="btn-icon" />
{t('selection.action.window.r_regenerate')}
</OpButton>
)}
<OpButton onClick={handleCopy} $isWindowFocus={isWindowFocus && !!content} data-hovered={isCopyHovered}> <OpButton onClick={handleCopy} $isWindowFocus={isWindowFocus && !!content} data-hovered={isCopyHovered}>
<Copy size={14} className="btn-icon" /> <Copy size={14} className="btn-icon" />
{t('selection.action.window.c_copy')} {t('selection.action.window.c_copy')}
@ -105,19 +144,28 @@ const WindowFooter: FC<FooterProps> = ({ content = '', loading = false, onPause
) )
} }
const Container = styled.div` const Container = styled.div<{ $isHovered: boolean }>`
position: absolute; position: absolute;
bottom: 0; bottom: 0;
left: 8px; left: 50%;
right: 8px; transform: translateX(-50%);
max-width: 480px;
min-width: min-content;
width: calc(100% - 16px);
display: flex; display: flex;
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
padding: 5px 0; padding: 5px 8px;
height: 32px; height: 32px;
backdrop-filter: blur(8px); backdrop-filter: blur(8px);
border-radius: 8px; border-radius: 8px;
opacity: 0;
transition: all 0.3s ease;
&:hover {
opacity: 1;
}
` `
const OpButtonWrapper = styled.div` const OpButtonWrapper = styled.div`
@ -144,6 +192,9 @@ const OpButton = styled.div<{ $isWindowFocus: boolean; $isHovered?: boolean }>`
opacity: ${(props) => (props.$isWindowFocus ? 1 : 0.2)}; opacity: ${(props) => (props.$isWindowFocus ? 1 : 0.2)};
transition: opacity 0.3s ease; transition: opacity 0.3s ease;
transition: color 0.2s ease; transition: color 0.2s ease;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
.btn-icon { .btn-icon {
color: var(--color-text-secondary); color: var(--color-text-secondary);